吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 14684|回复: 440
上一主题 下一主题
收起左侧

[Windows] 自动点击工具(图片识别与坐标点击)V1.3版

    [复制链接]
跳转到指定楼层
楼主
mzpyzj 发表于 2026-7-17 08:47 回帖奖励
本帖最后由 mzpyzj 于 2026-7-17 08:47 编辑

因为上个版本出现了保存配置会闪退的问题,但原贴(自动点击工具(图片识别与坐标点击)V1.2版 - 吾爱破解 - 52pojie.cn)已经不能够编辑了,所以这里重新开个新帖。但这一版本应该应该是最终版了,主要是没学过编程而借助AI来编写和调试太累人了。

版本1.3,更新时间:2026年7月17日
1.修复配置保存闪退问题。
2.“鼠标拖动”模块增加拖动持续时间(A坐标到B坐标拖动用时)。
3.“添加键盘”模块在原来的基础上增加另外两种按键模式,一是在按下设置的按键不松开,直到设置的时间到了才松开按键,二是按下设置的按键不松开,直到匹配到对应的图片后才松开按键(选择的图片可以先用“添加图像”按钮截图)。

一、工具简介与界面
本工具是基于 Python+PyQt5 开发的 Windows 自动化脚本工具,该工具集成了图像识别点击、坐标点击、按键与文字输入,可视化流程编排,可用于办公自动化、游戏挂机等场景,提升重复操作效率。

二、核心能力:
图像识别点击:通过截取界面图像,可在屏幕或指定窗口中自动寻找预设的图片目标并点击,自带相似度阈值调节。
固定坐标点击:采集屏幕 XY 坐标,运行时直接点击固定点位,无需识别图片。
键盘按键输入:可模拟单键、Ctrl/Alt/Shift 组合快捷键输入,提供 3 种按键工作模式:1.按键一次(按下立刻松开);2.长按按键到指定时间松开(按住按键固定毫秒后松开);3.按住直到图像出现(持续按住按键,实时循环识图,检测到目标截图后自动松开按键,未识别到会停在该步骤)
文字输入:可添加,一段文字内容,在运行时候模拟键盘自动输入自定义文本,支持单行、多行文字。
鼠标拖拽:模拟鼠标按住拖动动作,支持左键 / 右键拖拽,平滑移动无瞬移。
可视化流程编排:图形化步骤列表中支持增删和上下移动,每一步均可独立配置点击类型、等待时间、相似度等参数,并支持步骤跳转,实现条件分支逻辑。
全局热键控制:支持自定义全局快捷键(默认F12),可一键启动或停止任务无需切换窗口即可控制运行状态;后台窗口执行;指定窗口绑定(通过“选取窗口”功能锁定目标程序(HWND))和后台消息注入(使窗口处于后台或被遮挡,也能通过 Windows 消息机制模拟点击,不干扰前台工作,也不影响鼠标键盘的正常使用)。特别说明:直接从驱动层读取硬件输入(鼠标/键盘)的游戏则无法实现点击效果。
配置保存与复用:支持一键保存、加载配置文件。
灵活循环策略:支持定时执行、无限循环、指定次数循环,X轴与Y轴点击偏移量、每个步骤的随机延迟时间。
三、使用说明
控制流程:添加好步骤或加载配置后可以开始执行、暂停和停止执行。
文件操作:用于保存步骤列表的流程,便于下一次加载使用。
定时执行:用于设定未来时间的执行,设定好“开始时间”和“结束时间”并勾选“启用定时执行”后点击开始执行按钮就可以了,时间到了自动执行步骤列表的流程,系统时间超过设定结束时间后会立即终止所有操作。
目标窗口设置:用于后台执行点击,不影响操作,该工具点击原理依赖 Windows 消息队列,是通过 SendMessage发送消息给系统,系统会转发给窗口实现点击的。(注意:直接从驱动层读取硬件输入(鼠标/键盘中断)的游戏则无法实现点击效果)。
热键设置:用于一键开始执行或者暂停流程,可自定义录入热键,避免和某些案件冲突(热键默认为F12)。
高级设置:包含了流程的循环设置(可以设置步骤列表的执行次数,默认无限循环),点击偏移设置(用于模拟坐标点(图像的中心位置)进行鼠标点击的上下左右偏移,通过随机微调点击位置,避免一些游戏对鼠标操作的检测)、随机延迟点击设置(在步骤列表中设置的等待时间的基础上再增加随机延时,避免点击时间固定,降低被检测识别风险,默认随机延迟时间是在100毫秒至1000毫秒之间)
操作步骤:添加图像(通过截取图像进行识别点击),添加坐标(在屏幕选点截取坐标),添加键盘(模拟单个按键或者Ctrl/Alt/Shift 组合快捷键,有3种模式),添加文字(可用于一段文字输出),鼠标拖动(模拟鼠标拖动),上移和下移(用于调整步骤的执行顺序),删除(用于删除不要的步骤)。(详见核心能力的说明)
步骤列表:步骤(流程执行的顺序),类型(图像识别点击和坐标点击),点击类型(左键单击、右键单击、双击、跳转,其中跳转是用于类型是图像识别,就是该步骤识别到图像就跳转到对应步骤去),等待(执行等待时间,1秒等于1000毫秒,默认1000毫秒),相似度(图像识别的相似度,默认0.8),接受偏移和接受随机延迟(可自定义步骤是否进行鼠标点击的偏移和点击延迟,默认勾选,但需要自行勾选上“启动偏移”和“启动随机延迟”),跳转设置(只能在“点击类型”为跳转的情况下才能设置为跳转步骤)。

历史更新日志:
版本1.2,更新时间:2026年6月30日
1. 新增定时执行功能。支持在指定时间段内自动执行流程。如果当前时间早于开始时间,会等待直到开始时间,如果超过结束时间,自动停止执行。注:勾选并设置好的开始和结束时间后,还要点击“开始执行”按钮;
2. 新增文字输入功能。增加了"添加文字"按钮,必须输入文字才能点击确定;
3. 新增鼠标拖动功能。增加了"鼠标拖动"按钮,需要连续采集两个坐标点(起点和终点),在步骤列表中,"点击类型"列可选择"左键拖动"或"右键拖动"。执行时实现:在起点按下选定鼠标键→移动到终点→释放鼠标键;
4.重新优化UI界面。因左侧UI界面拥挤,已将“循环设置”“偏移设置”“随机延迟设置”放入“其他设置内”内;
5.热键设置改为支持自定义设置。点击“录制热键”按键后改按钮会变成红色,在按下你需要设置的热键即可。且程序启动时自动加载上次保存的设置。
版本1.1,更新时间:2026年6月17日
1.新增“添加键盘”按钮:在“步骤操作”区域新增“ 添加键盘”按钮,支持添加键盘操作作为独立步骤。
2.条件跳转功能优化,新增“找到跳转”和“未找到跳转”列。在步骤列表中,图像识别步骤新增两列下拉框,找到跳转(若图像匹配成功,跳转到指定步骤)和未找到跳转(若图像未匹配成功,跳转到指定步骤)。
3.UI界面调整,并移除固定尺寸,允许拖动窗口边缘调整大小。




蓝奏云下载地址:https://zhangjie.lanzouu.com/iVBtH3wtptgf
密码:52pj

使用该工具后觉得还行的话,可以回来免费评分一下。



免费评分

参与人数 201吾爱币 +181 热心值 +174 收起 理由
shaco + 1 + 1 谢谢@Thanks!
ZLSQQ + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
sssqy + 1 + 1 我很赞同!
aixueli + 1 用心讨论,共获提升!
v8888800 + 1 + 1 我很赞同!
xingyuhang + 1 + 1 谢谢@Thanks!
yx19880111 + 1 + 1 谢谢@Thanks!
天隐 + 1 + 1 谢谢@Thanks!
thinkerdai + 2 + 1 可以增加滑轮功能吗,谢谢
超高校级的非命 + 1 + 1 谢谢@Thanks!
kaixin94w + 1 + 1 我很赞同!
weiwei321 + 1 + 1 我很赞同!
shanshan258 + 1 + 1 我很赞同!
keshi0 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
81228541 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
xinxin2254 + 1 我很赞同!
rjc000 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
zhenqi + 1 + 1 我很赞同!
eCAN + 1 + 1 我很赞同!
固相膜 + 1 + 1 我很赞同!
Mr.Lemon + 1 + 1 谢谢@Thanks!
pojieh + 1 谢谢@Thanks!
gele1204 + 1 我很赞同!
haigang + 1 + 1 我很赞同!
liu199597 + 1 我很赞同!
oudeng + 1 热心回复!
笑战天下88 + 1 + 1 谢谢@Thanks!
liuxinzhang + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
rockyfield + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
jinzhang313 + 1 + 1 我很赞同!
wjxwww2026 + 1 谢谢@Thanks!
jinye9527 + 1 + 1 我很赞同!
shagua + 1 + 1 热心回复!
btcjg945 + 1 + 1 谢谢@Thanks!
weekind + 1 + 1 我很赞同!
freewilson + 1 + 1 我很赞同!
feelsg + 1 + 1 我很赞同!
leiao007 + 1 谢谢@Thanks!
周坤 + 1 + 1 用心讨论,共获提升!
花开寺海 + 1 谢谢@Thanks!
cg8322142 + 1 + 1 谢谢@Thanks!
nekoneko2021 + 1 + 1 谢谢@Thanks!
h07799486 + 1 + 1 谢谢@Thanks!
cbdmgm + 1 谢谢@Thanks!
grrr_zhao + 1 + 1 谢谢@Thanks!
duzuiqingfeng + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
开心熊猫741 + 1 + 1 我很赞同!
zhang0627 + 1 + 1 谢谢@Thanks!
懒鸟 + 1 你把他改成直接可以用PYTHON代码,代码里面还能调用库,这样就牛逼了,哈哈 ...
ouyu + 1 谢谢@Thanks!
9898a + 1 + 1 我很赞同!
epsonc43 + 1 热心回复!
daidaiyy + 1 我很赞同!
magicway + 1 + 1 谢谢@Thanks!
不二 + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
pl3000 + 1 + 1 有安卓版的吗?
c672569644 + 1 + 1 谢谢@Thanks!
初夏小雨 + 1 + 1 谢谢@Thanks!
hushuai450 + 1 + 1 我很赞同!
瞎溜达 + 1 + 1 谢谢@Thanks!
fjptcz + 1 + 1 没有半点判断的功能啊。。。。
askhcy + 1 + 1 我很赞同!
lp5370 + 1 用心讨论,共获提升!
pass123 + 1 我很赞同!
Mottt00 + 1 + 1 谢谢@Thanks!
itspurse + 1 + 1 我很赞同!
ghm18888 + 1 谢谢@Thanks!
white526 + 1 我很赞同!
gushiyu + 1 + 1 谢谢@Thanks!
qhrho + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
atom000 + 1 + 1 谢谢@Thanks!
jhway + 1 + 1 我很赞同!
睡懒觉 + 1 我很赞同!
wanfen11 + 1 我很赞同!
shadowaaa + 1 + 1 谢谢@Thanks!
baikaka6 + 1 + 1 谢谢@Thanks!
半城长眠 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
aqrookie + 1 + 1 谢谢@Thanks!
ghgf1238888 + 1 + 1 谢谢@Thanks!
jztom + 1 + 1 我很赞同!
haiyangnanzi + 1 + 1 谢谢@Thanks!
X0819_OvO + 1 + 1 谢谢@Thanks!
xunmiyixia + 1 谢谢@Thanks!
rebirthboy + 1 我很赞同!
cctvlive + 1 用心讨论,共获提升!
小单单大蛋蛋 + 1 + 1 热心回复!
a520 + 1 + 1 我很赞同!
kuraih + 1 + 1 能用这个interception的代替API级的输入模拟吗
wuaileo + 1 + 1 谢谢@Thanks!
Natu + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
huigul123 + 1 + 1 谢谢@Thanks!
huafeng + 1 谢谢@Thanks!
Xuyibo990709 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
xiaoyaodulang + 1 + 1 谢谢@Thanks!
大丨表哥 + 1 + 1 我很赞同!
nojon + 1 + 1 谢谢@Thanks!
ploiu + 1 + 1 谢谢@Thanks!
zxcvb1234363 + 2 + 1 谢谢@Thanks!
sky315101 + 1 谢谢@Thanks!
jg518 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| mzpyzj 发表于 2026-7-17 11:23 |楼主
5588406 发表于 2026-7-17 11:10
楼主,我最近在学习python,你这个PyQt5怎么做啊,有没有好一点的教程?你这个程序的源码可以借来学习一下吗 ...

我用的是Visual Studio Code进行调试和打包的,没有教程,纯靠问AI
[Asm] 纯文本查看 复制代码
import sys
import cv2
import numpy as np
import json
import os
import time
import random
import ctypes
import win32gui
import win32con
import win32api
import shutil
from PIL import ImageGrab
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from pynput.mouse import Controller as MouseController, Listener as MouseListener, Button
from pynput.keyboard import Controller as KeyboardController, Listener as KeyboardListener, Key

def get_app_base_dir():
    """获取应用程序基础目录,兼容脚本运行和PyInstaller打包"""
    if getattr(sys, 'frozen', False):
        return os.path.dirname(sys.executable)
    else:
        return os.path.dirname(os.path.abspath(__file__))

# 设置 DPI 感知
try:
    ctypes.windll.shcore.SetProcessDpiAwareness(2)
except:
    try:
        ctypes.windll.user32.SetProcessDPIAware()
    except:
        pass


class Step:
    def __init__(self, step_type, image_path=None, x=None, y=None,
                 click_type='left', wait_time=1000, similarity=0.8,
                 random_delay_enabled=False, random_delay_min=100, random_delay_max=1000,
                 accept_offset=True, accept_random_delay=True,
                 jump_on_found=None, jump_on_not_found=None,
                 key_sequence=None, text_content=None,
                 drag_start_x=None, drag_start_y=None, drag_end_x=None, drag_end_y=None,
                 # 键盘操作相关字段
                 key_action_type=0,  # 0=按键一次, 1=长按指定时间, 2=按住直到图像出现
                 key_hold_time=0,  # 按键按住时间(毫秒)
                 key_hold_image_path=None,  # 按住直到出现的图像路径
                 # 拖动缓冲相关
                 drag_start_delay=0,  # 拖动开始前的延迟(毫秒)
                 drag_duration=1000):  # 拖动持续时间(毫秒),默认1秒
        self.step_type = step_type
        self.image_path = image_path
        self.x = x
        self.y = y
        self.click_type = click_type
        self.wait_time = wait_time
        self.similarity = similarity
        self.random_delay_enabled = random_delay_enabled
        self.random_delay_min = random_delay_min
        self.random_delay_max = random_delay_max
        self.accept_offset = accept_offset
        self.accept_random_delay = accept_random_delay
        self.jump_on_found = jump_on_found
        self.jump_on_not_found = jump_on_not_found
        self.key_sequence = key_sequence
        self.text_content = text_content
        # 鼠标拖动相关
        self.drag_start_x = drag_start_x
        self.drag_start_y = drag_start_y
        self.drag_end_x = drag_end_x
        self.drag_end_y = drag_end_y
        # 键盘操作相关字段初始化
        self.key_action_type = key_action_type
        self.key_hold_time = key_hold_time
        self.key_hold_image_path = key_hold_image_path
        # 拖动缓冲相关字段初始化
        self.drag_start_delay = drag_start_delay
        self.drag_duration = drag_duration


class AutoClickerApp(QMainWindow):
    coordinate_captured = pyqtSignal(int, int)
    stop_coordinate_mode = pyqtSignal()
    stop_select_window_mode_signal = pyqtSignal()
    screenshot_closed = pyqtSignal()
    drag_coordinate_captured = pyqtSignal(int, int)
    hotkey_recorded = pyqtSignal(str)
    # 新增信号:用于键盘对话框中的图像选择
    keyboard_image_selected = pyqtSignal(str)

    # 参考分辨率和默认尺寸
    REFERENCE_WIDTH = 1920
    REFERENCE_HEIGHT = 1080
    DEFAULT_WIDTH = 1000
    DEFAULT_HEIGHT = 785
    
    # 最小尺寸限制
    MIN_WIDTH = 800
    MIN_HEIGHT = 600

    def __init__(self):
        super().__init__()
        
        # 获取应用程序基础目录
        self.app_base_dir = get_app_base_dir()
        
        # 定义配置文件路径(始终在EXE/脚本所在目录)
        self.config_file = os.path.join(self.app_base_dir, "autoclicker_config.json")
        self.settings_file = os.path.join(self.app_base_dir, "autoclicker_settings.json")
        self.screenshot_dir = os.path.join(self.app_base_dir, "screenshots")
        self.first_run_flag_file = os.path.join(self.app_base_dir, ".first_run_flag")
        
        # 加载窗口尺寸
        self.window_width, self.window_height = self.load_window_size()
        
        self.setWindowTitle("自动点击工具(图片识别与坐标点击)V1.3--吾爱破解论坛")
        
        # 获取屏幕信息
        self.screen = QApplication.primaryScreen()
        self.screen_geometry = self.screen.availableGeometry()
        self.screen_width = self.screen_geometry.width()
        self.screen_height = self.screen_geometry.height()
        
        # 计算缩放比例
        self.scale_factor = self.calculate_scale_factor()
        
        # 应用缩放
        self.scaled_width = int(self.window_width * self.scale_factor)
        self.scaled_height = int(self.window_height * self.scale_factor)
        
        # 确保窗口不会超出屏幕
        self.scaled_width = min(self.scaled_width, int(self.screen_width * 0.95))
        self.scaled_height = min(self.scaled_height, int(self.screen_height * 0.95))
        
        # 设置窗口尺寸
        self.resize(self.scaled_width, self.scaled_height)
        self.setMinimumSize(
            int(self.MIN_WIDTH * self.scale_factor),
            int(self.MIN_HEIGHT * self.scale_factor)
        )
        
        # 居中显示
        self.center_on_screen()

        self.mouse_listener = None
        self.keyboard_listener = None
        self.global_hotkey_listener = None
        self.target_hwnd = None
        self.steps = []  # 初始化步骤列表
        
        # 定时执行相关
        self.timed_execution_enabled = False
        self.start_datetime = None
        self.end_datetime = None
        
        # 运行状态
        self.is_running = False
        self.is_paused = False
        self.current_step_index = 0
        
        # 默认设置值
        self.loop_type = 0  # 0=无限循环, 1=次数循环
        self.loop_count = 1000
        self.offset_enabled = False
        self.offset_x = 5
        self.offset_y = 5
        self.random_delay_enabled = False
        self.random_delay_min = 100
        self.random_delay_max = 1000

        # 创建必要的目录
        os.makedirs(self.screenshot_dir, exist_ok=True)

        self.mouse = MouseController()
        self.keyboard = KeyboardController()
        self.hotkey = "F12"

        # 加载全局设置
        self.load_global_settings()
        self.init_ui()
        self.setup_global_hotkey()

        self.execution_thread = ExecutionThread(self)
        self.execution_thread.finished.connect(self.on_execution_finished)
        self.execution_thread.error.connect(self.on_execution_error)
        self.execution_thread.step_completed.connect(self.on_step_completed)

        self.coordinate_captured.connect(self.on_coordinate_captured)
        self.stop_coordinate_mode.connect(self.stop_add_coordinate_mode)
        self.stop_select_window_mode_signal.connect(self.stop_select_window_mode)
        self.screenshot_closed.connect(self.on_screenshot_closed)
        self.drag_coordinate_captured.connect(self.on_drag_coordinate_captured)
        self.hotkey_recorded.connect(self.on_hotkey_recorded)
        self.keyboard_image_selected.connect(self.on_keyboard_image_selected)
        
        # 检查是否首次运行,如果是则显示声明弹窗
        self.check_first_run()

    def check_first_run(self):
        """检查是否首次运行,如果是则显示声明弹窗"""
        if not os.path.exists(self.first_run_flag_file):
            # 显示声明弹窗
            self.show_first_run_dialog()
            # 创建标志文件,表示已经运行过
            try:
                with open(self.first_run_flag_file, 'w', encoding='utf-8') as f:
                    f.write("First run completed.")
            except Exception as e:
                print(f"创建首次运行标志文件失败: {e}")

    def show_first_run_dialog(self):
        """显示首次运行声明弹窗"""
        dialog = QDialog(self)
        dialog.setWindowTitle("自动点击工具声明")
        dialog.setModal(True)
        dialog.setFixedSize(500, 300)
        
        # 设置样式
        dialog.setStyleSheet("""
            QDialog {
                background-color: #f8f9fa;
            }
            QLabel#titleLabel {
                font-size: 24px;
                font-weight: bold;
                color: #2c3e50;
                padding: 10px;
            }
            QLabel#contentLabel {
                font-size: 16px;
                color: #34495e;
                padding: 5px;
                line-height: 1.5;
            }
            QPushButton {
                background-color: #3498db;
                color: white;
                border: none;
                padding: 10px 20px;
                font-size: 14px;
                border-radius: 5px;
                min-width: 100px;
            }
            QPushButton:hover {
                background-color: #2980b9;
            }
            QPushButton:pressed {
                background-color: #21618c;
            }
        """)
        
        layout = QVBoxLayout(dialog)
        layout.setSpacing(20)
        layout.setContentsMargins(30, 30, 30, 30)
        
        # 标题
        title_label = QLabel("🎉 欢迎使用自动点击工具")
        title_label.setObjectName("titleLabel")
        title_label.setAlignment(Qt.AlignCenter)
        layout.addWidget(title_label)
        
        # 内容
        content_label = QLabel()
        content_label.setObjectName("contentLabel")
        content_label.setText("""
        <div style='text-align: center;'>
            <p style='margin: 10px 0;'>本工具出自吾爱破解论坛</p>
            <p style='margin: 10px 0;'>本软件完全免费,仅供学习和技术交流使用</p>
            <p style='margin: 10px 0; color: #e74c3c; font-weight: bold;'>
                请勿将其用于任何违反服务条款或法律法规的活动
            </p>
        </div>
        """)
        content_label.setWordWrap(True)
        content_label.setAlignment(Qt.AlignCenter)
        layout.addWidget(content_label)
        
        # 按钮
        button_layout = QHBoxLayout()
        button_layout.addStretch()
        
        ok_button = QPushButton("我知道了")
        ok_button.clicked.connect(dialog.accept)
        button_layout.addWidget(ok_button)
        
        button_layout.addStretch()
        layout.addLayout(button_layout)
        
        # 显示弹窗
        dialog.exec_()

    def calculate_scale_factor(self):
        """计算基于参考分辨率的缩放比例"""
        # 使用宽度和高度中较小的缩放比例,确保窗口完全可见
        width_scale = self.screen_width / self.REFERENCE_WIDTH
        height_scale = self.screen_height / self.REFERENCE_HEIGHT
        return min(width_scale, height_scale)

    def center_on_screen(self):
        """将窗口居中显示在屏幕上"""
        x = (self.screen_width - self.width()) // 2
        y = (self.screen_height - self.height()) // 2
        # 确保不会超出屏幕顶部
        y = max(0, y)
        self.move(x, y)

    def load_window_size(self):
        """加载保存的窗口尺寸"""
        try:
            if os.path.exists(self.settings_file):
                with open(self.settings_file, 'r', encoding='utf-8') as f:
                    settings = json.load(f)
                    width = settings.get("window_width", self.DEFAULT_WIDTH)
                    height = settings.get("window_height", self.DEFAULT_HEIGHT)
                    return width, height
        except Exception as e:
            print(f"加载窗口尺寸失败: {e}")
        return self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT

    def save_window_size(self):
        """保存当前窗口尺寸"""
        # 保存原始尺寸(未缩放的)
        unscaled_width = int(self.width() / self.scale_factor)
        unscaled_height = int(self.height() / self.scale_factor)
        
        try:
            settings = {}
            if os.path.exists(self.settings_file):
                with open(self.settings_file, 'r', encoding='utf-8') as f:
                    settings = json.load(f)
            
            settings["window_width"] = unscaled_width
            settings["window_height"] = unscaled_height
            
            with open(self.settings_file, 'w', encoding='utf-8') as f:
                json.dump(settings, f, indent=2, ensure_ascii=False)
            print(f"设置已保存到: {self.settings_file}")
        except Exception as e:
            print(f"保存窗口尺寸失败: {e}")

    def scaled_value(self, value):
        """返回缩放后的值"""
        return int(value * self.scale_factor)

    def init_ui(self):
        # 根据缩放比例调整字体
        base_font_size = 10
        scaled_font_size = max(8, int(base_font_size * self.scale_factor))
        font = QFont("Microsoft YaHei", scaled_font_size)
        self.setFont(font)

        main_widget = QWidget()
        main_layout = QVBoxLayout()
        main_layout.setSpacing(self.scaled_value(10))
        main_layout.setContentsMargins(
            self.scaled_value(10),
            self.scaled_value(10),
            self.scaled_value(10),
            self.scaled_value(10)
        )

        main_splitter = QSplitter(Qt.Horizontal)

        left_widget = QWidget()
        left_layout = QVBoxLayout()
        left_layout.setSpacing(self.scaled_value(10))
        left_widget.setMinimumWidth(self.scaled_value(320))
        left_widget.setMaximumWidth(self.scaled_value(380))

        # 1. 流程控制模块
        control_group = QGroupBox("流程控制")
        control_layout = QVBoxLayout()
        control_layout.setSpacing(self.scaled_value(5))
        
        self.start_btn = QPushButton("&#9654; 开始执行")
        self.start_btn.setMinimumHeight(self.scaled_value(35))
        self.start_btn.clicked.connect(self.start_execution)
        
        self.stop_btn = QPushButton("&#9209; 停止执行")
        self.stop_btn.setMinimumHeight(self.scaled_value(35))
        self.stop_btn.clicked.connect(self.stop_execution)
        self.stop_btn.setEnabled(False)
        
        self.pause_btn = QPushButton("&#9208; 暂停执行")
        self.pause_btn.setMinimumHeight(self.scaled_value(35))
        self.pause_btn.clicked.connect(self.pause_execution)
        self.pause_btn.setEnabled(False)
        
        control_layout.addWidget(self.start_btn)
        stop_pause_layout = QHBoxLayout()
        stop_pause_layout.addWidget(self.pause_btn)
        stop_pause_layout.addWidget(self.stop_btn)
        control_layout.addLayout(stop_pause_layout)
        control_group.setLayout(control_layout)
        left_layout.addWidget(control_group)

        # 2. 文件操作模块
        file_group = QGroupBox("文件操作")
        file_layout = QHBoxLayout()
        file_layout.setSpacing(self.scaled_value(5))
        
        self.save_btn = QPushButton("&#128190; 保存配置")
        self.save_btn.setMinimumHeight(self.scaled_value(35))
        self.save_btn.clicked.connect(self.save_config)
        
        self.load_btn = QPushButton("&#128194; 加载配置")
        self.load_btn.setMinimumHeight(self.scaled_value(35))
        self.load_btn.clicked.connect(self.load_config)
        
        file_layout.addWidget(self.save_btn)
        file_layout.addWidget(self.load_btn)
        file_group.setLayout(file_layout)
        left_layout.addWidget(file_group)

        # 3. 定时执行模块
        timed_group = QGroupBox("定时执行")
        timed_layout = QVBoxLayout()
        timed_layout.setSpacing(self.scaled_value(5))
        
        self.timed_execution_checkbox = QCheckBox("启用定时执行")
        self.timed_execution_checkbox.setChecked(self.timed_execution_enabled)
        self.timed_execution_checkbox.stateChanged.connect(self.toggle_timed_execution)
        timed_layout.addWidget(self.timed_execution_checkbox)
        
        datetime_layout1 = QHBoxLayout()
        datetime_layout1.addWidget(QLabel("开始时间:"))
        self.start_datetime_edit = QDateTimeEdit()
        self.start_datetime_edit.setCalendarPopup(True)
        self.start_datetime_edit.setDisplayFormat("yyyy-MM-dd HH:mm")
        if self.start_datetime:
            self.start_datetime_edit.setDateTime(self.start_datetime)
        else:
            self.start_datetime_edit.setDateTime(QDateTime.currentDateTime())
        datetime_layout1.addWidget(self.start_datetime_edit)
        timed_layout.addLayout(datetime_layout1)
        
        datetime_layout2 = QHBoxLayout()
        datetime_layout2.addWidget(QLabel("结束时间:"))
        self.end_datetime_edit = QDateTimeEdit()
        self.end_datetime_edit.setCalendarPopup(True)
        self.end_datetime_edit.setDisplayFormat("yyyy-MM-dd HH:mm")
        if self.end_datetime:
            self.end_datetime_edit.setDateTime(self.end_datetime)
        else:
            self.end_datetime_edit.setDateTime(QDateTime.currentDateTime().addSecs(3600))
        datetime_layout2.addWidget(self.end_datetime_edit)
        timed_layout.addLayout(datetime_layout2)
        
        timed_group.setLayout(timed_layout)
        left_layout.addWidget(timed_group)

        # 4. 目标窗口设置模块
        window_group = QGroupBox("目标窗口设置 (后台执行关键)")
        window_layout = QVBoxLayout()
        window_layout.setSpacing(self.scaled_value(5))
        
        self.window_title_edit = QLineEdit()
        self.window_title_edit.setPlaceholderText("点击下方按钮选择目标窗口")
        self.window_title_edit.setReadOnly(True)
        self.window_title_edit.setMinimumHeight(self.scaled_value(30))

        self.select_window_btn = QPushButton("&#127919; 选取窗口")
        self.select_window_btn.setMinimumHeight(self.scaled_value(35))
        self.select_window_btn.clicked.connect(self.start_select_window_mode)

        self.clear_window_btn = QPushButton("&#10060; 清除窗口")
        self.clear_window_btn.setMinimumHeight(self.scaled_value(35))
        self.clear_window_btn.clicked.connect(self.clear_target_window)

        window_layout.addWidget(self.window_title_edit)
        window_layout.addWidget(self.select_window_btn)
        window_layout.addWidget(self.clear_window_btn)
        window_group.setLayout(window_layout)
        left_layout.addWidget(window_group)

        # 5. 热键设置模块
        hotkey_group = QGroupBox("热键设置")
        hotkey_layout = QVBoxLayout()
        hotkey_layout.setSpacing(self.scaled_value(5))
        
        hotkey_layout.addWidget(QLabel("启动/停止热键:"))
        
        self.hotkey_display = QLineEdit()
        self.hotkey_display.setReadOnly(True)
        self.hotkey_display.setText(self.hotkey)
        self.hotkey_display.setMinimumHeight(self.scaled_value(35))
        self.hotkey_display.setStyleSheet(f"""
            QLineEdit {{
                padding: {self.scaled_value(8)}px;
                font-size: {self.scaled_value(14)}px;
                font-weight: bold;
                background-color: #f0f0f0;
                border: 1px solid #ccc;
                border-radius: {self.scaled_value(3)}px;
            }}
        """)
        hotkey_layout.addWidget(self.hotkey_display)
        
        self.record_hotkey_btn = QPushButton("&#127908; 录制热键")
        self.record_hotkey_btn.setMinimumHeight(self.scaled_value(35))
        self.record_hotkey_btn.clicked.connect(self.start_record_hotkey)
        self.record_hotkey_btn.setStyleSheet(f"""
            QPushButton {{
                padding: {self.scaled_value(8)}px;
                font-size: {self.scaled_value(12)}px;
                background-color: #4CAF50;
                color: white;
                border: none;
                border-radius: {self.scaled_value(3)}px;
            }}
            QPushButton:hover {{
                background-color: #45a049;
            }}
            QPushButton:pressed {{
                background-color: #3d8b40;
            }}
        """)
        hotkey_layout.addWidget(self.record_hotkey_btn)
        
        hotkey_group.setLayout(hotkey_layout)
        left_layout.addWidget(hotkey_group)

        # 6. 其他设置按钮
        other_settings_group = QGroupBox("高级设置")
        other_settings_layout = QVBoxLayout()
        
        self.other_settings_btn = QPushButton("&#9881;&#65039; 其他设置")
        self.other_settings_btn.setMinimumHeight(self.scaled_value(40))
        self.other_settings_btn.clicked.connect(self.open_other_settings)
        self.other_settings_btn.setStyleSheet(f"""
            QPushButton {{
                padding: {self.scaled_value(10)}px;
                font-size: {self.scaled_value(12)}px;
                background-color: #2196F3;
                color: white;
                border: none;
                border-radius: {self.scaled_value(3)}px;
            }}
            QPushButton:hover {{
                background-color: #1976D2;
            }}
            QPushButton:pressed {{
                background-color: #1565C0;
            }}
        """)
        other_settings_layout.addWidget(self.other_settings_btn)
        
        other_settings_group.setLayout(other_settings_layout)
        left_layout.addWidget(other_settings_group)

        left_layout.addStretch()
        left_widget.setLayout(left_layout)

        right_widget = QWidget()
        right_layout = QVBoxLayout()
        right_layout.setSpacing(self.scaled_value(10))

        top_widget = QWidget()
        top_layout = QHBoxLayout()
        top_layout.setSpacing(self.scaled_value(10))

        step_ops_group = QGroupBox("步骤操作")
        step_ops_layout = QGridLayout()
        step_ops_layout.setSpacing(self.scaled_value(5))
        
        self.add_image_btn = QPushButton("&#128247; 添加图像")
        self.add_image_btn.setMinimumHeight(self.scaled_value(40))
        self.add_image_btn.clicked.connect(self.add_image_step)
        step_ops_layout.addWidget(self.add_image_btn, 0, 0)
        
        self.add_coordinate_btn = QPushButton("&#127919; 添加坐标")
        self.add_coordinate_btn.setMinimumHeight(self.scaled_value(40))
        self.add_coordinate_btn.clicked.connect(self.start_add_coordinate_mode)
        step_ops_layout.addWidget(self.add_coordinate_btn, 0, 1)
        
        self.add_keyboard_btn = QPushButton("&#9000;&#65039; 添加键盘")
        self.add_keyboard_btn.setMinimumHeight(self.scaled_value(40))
        self.add_keyboard_btn.clicked.connect(self.add_keyboard_step)
        step_ops_layout.addWidget(self.add_keyboard_btn, 1, 0)
        
        self.add_text_btn = QPushButton("&#128221; 添加文字")
        self.add_text_btn.setMinimumHeight(self.scaled_value(40))
        self.add_text_btn.clicked.connect(self.add_text_step)
        step_ops_layout.addWidget(self.add_text_btn, 1, 1)
        
        self.add_drag_btn = QPushButton("&#128433;&#65039; 鼠标拖动")
        self.add_drag_btn.setMinimumHeight(self.scaled_value(40))
        self.add_drag_btn.clicked.connect(self.start_add_drag_mode)
        step_ops_layout.addWidget(self.add_drag_btn, 2, 0)
        
        self.delete_btn = QPushButton("&#128465;&#65039; 删除")
        self.delete_btn.setMinimumHeight(self.scaled_value(40))
        self.delete_btn.clicked.connect(self.delete_step)
        step_ops_layout.addWidget(self.delete_btn, 2, 1)
        
        self.move_up_btn = QPushButton("&#11014;&#65039; 上移")
        self.move_up_btn.setMinimumHeight(self.scaled_value(40))
        self.move_up_btn.clicked.connect(self.move_step_up)
        step_ops_layout.addWidget(self.move_up_btn, 3, 0)
        
        self.move_down_btn = QPushButton("&#11015;&#65039; 下移")
        self.move_down_btn.setMinimumHeight(self.scaled_value(40))
        self.move_down_btn.clicked.connect(self.move_step_down)
        step_ops_layout.addWidget(self.move_down_btn, 3, 1)
        
        step_ops_group.setLayout(step_ops_layout)
        top_layout.addWidget(step_ops_group)

        preview_group = QGroupBox("预览区域")
        preview_layout = QVBoxLayout()
        self.preview_label = QLabel("预览区域")
        self.preview_label.setAlignment(Qt.AlignCenter)
        self.preview_label.setMinimumHeight(self.scaled_value(150))
        self.preview_label.setMaximumHeight(self.scaled_value(150))
        self.preview_label.setStyleSheet(
            f"border: 1px solid #ccc; border-radius: {self.scaled_value(3)}px; "
            f"background-color: #f9f9f9; font-size: {self.scaled_value(11)}px; padding: {self.scaled_value(5)}px;"
        )
        preview_layout.addWidget(self.preview_label)
        preview_group.setLayout(preview_layout)
        top_layout.addWidget(preview_group)

        top_widget.setLayout(top_layout)
        right_layout.addWidget(top_widget)

        steps_group = QGroupBox("步骤列表")
        steps_layout = QVBoxLayout()
        self.steps_table = QTableWidget()
        self.steps_table.setColumnCount(9)
        self.steps_table.setHorizontalHeaderLabels([
            "步骤", "类型", "点击类型", "等待(ms)",
            "相似度", "接受偏移", "接受随机延迟", 
            "找到跳转", "未找到跳转"
        ])

        header = self.steps_table.horizontalHeader()
        header.setSectionResizeMode(QHeaderView.Stretch)
        header.setSectionsClickable(False)
        header.setSectionsMovable(False)

        self.steps_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.steps_table.setAlternatingRowColors(True)
        self.steps_table.setStyleSheet(f"""
            QTableWidget {{
                gridline-color: #ddd;
                font-size: {self.scaled_value(11)}px;
                border: 1px solid #ccc;
                border-radius: {self.scaled_value(3)}px;
            }}
            QTableWidget::item {{
                padding: {self.scaled_value(6)}px {self.scaled_value(4)}px;
                min-height: {self.scaled_value(20)}px;
                text-align: center;
            }}
            QTableWidget::item:selected {{
                background-color: #e3f2fd;
            }}
            QHeaderView::section {{
                background-color: #f0f0f0;
                padding: {self.scaled_value(6)}px;
                border: 1px solid #ddd;
                font-weight: bold;
                text-align: center;
            }}
        """)
        self.steps_table.verticalHeader().setDefaultSectionSize(self.scaled_value(35))
        self.steps_table.verticalHeader().setVisible(False)
        self.steps_table.cellClicked.connect(self.on_step_selected_from_table)
        steps_layout.addWidget(self.steps_table)
        steps_group.setLayout(steps_layout)
        right_layout.addWidget(steps_group)

        right_widget.setLayout(right_layout)

        main_splitter.addWidget(left_widget)
        main_splitter.addWidget(right_widget)
        main_splitter.setSizes([self.scaled_value(350), self.scaled_value(850)])

        main_layout.addWidget(main_splitter)
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

    def resizeEvent(self, event):
        """窗口大小改变时保存尺寸"""
        super().resizeEvent(event)
        # 只在非最大化状态下保存尺寸
        if not self.isMaximized():
            self.save_window_size()

    def open_other_settings(self):
        """打开其他设置对话框"""
        dialog = OtherSettingsDialog(self)
        dialog.exec_()

    def start_record_hotkey(self):
        """开始录制热键"""
        self.is_recording_hotkey = True
        self.record_hotkey_btn.setText("&#127908; 正在录制...按ESC取消")
        self.record_hotkey_btn.setStyleSheet(f"""
            QPushButton {{
                padding: {self.scaled_value(8)}px;
                font-size: {self.scaled_value(12)}px;
                background-color: #f44336;
                color: white;
                border: none;
                border-radius: {self.scaled_value(3)}px;
            }}
            QPushButton:hover {{
                background-color: #da190b;
            }}
        """)
        self.hotkey_display.setText("请按下组合键...")
        
        # 启动键盘监听
        self.hotkey_listener = KeyboardListener(on_press=self.on_hotkey_record_press)
        self.hotkey_listener.start()

    def on_hotkey_record_press(self, key):
        """录制热键时的按键回调"""
        if not self.is_recording_hotkey:
            return False
            
        # 按ESC取消录制
        if key == Key.esc:
            self.stop_record_hotkey()
            return False
            
        # 记录热键
        try:
            keys = []
            if self.keyboard.ctrl_pressed:
                keys.append("Ctrl")
            if self.keyboard.alt_pressed:
                keys.append("Alt")
            if self.keyboard.shift_pressed:
                keys.append("Shift")
                
            # 添加主键
            if hasattr(key, 'char') and key.char:
                keys.append(key.char.upper())
            elif hasattr(key, 'name'):
                keys.append(key.name.capitalize())
                
            if keys:
                hotkey_str = "+".join(keys)
                self.hotkey_recorded.emit(hotkey_str)
                return False
        except:
            pass
        return True

    def on_hotkey_recorded(self, hotkey_str):
        """热键录制完成"""
        self.hotkey = hotkey_str
        self.hotkey_display.setText(hotkey_str)
        self.stop_record_hotkey()
        self.setup_global_hotkey()

    def stop_record_hotkey(self):
        """停止录制热键"""
        self.is_recording_hotkey = False
        if hasattr(self, 'hotkey_listener'):
            self.hotkey_listener.stop()
            
        self.record_hotkey_btn.setText("&#127908; 录制热键")
        self.record_hotkey_btn.setStyleSheet(f"""
            QPushButton {{
                padding: {self.scaled_value(8)}px;
                font-size: {self.scaled_value(12)}px;
                background-color: #4CAF50;
                color: white;
                border: none;
                border-radius: {self.scaled_value(3)}px;
            }}
            QPushButton:hover {{
                background-color: #45a049;
            }}
            QPushButton:pressed {{
                background-color: #3d8b40;
            }}
        """)

    def toggle_timed_execution(self, state):
        self.timed_execution_enabled = (state == Qt.Checked)

    def load_global_settings(self):
        """加载全局设置(热键等)"""
        try:
            if os.path.exists(self.settings_file):
                with open(self.settings_file, 'r', encoding='utf-8') as f:
                    settings = json.load(f)
                    self.hotkey = settings.get("hotkey", "F12")
                    
                    # 加载定时设置
                    self.timed_execution_enabled = settings.get("timed_execution_enabled", False)
                    start_time_str = settings.get("start_datetime", "")
                    end_time_str = settings.get("end_datetime", "")
                    
                    # 尝试解析时间字符串,支持新旧两种格式
                    if start_time_str:
                        # 先尝试新格式(精确到分钟)
                        self.start_datetime = QDateTime.fromString(start_time_str, "yyyy-MM-dd HH:mm")
                        if not self.start_datetime.isValid():
                            # 尝试旧格式(精确到秒)
                            self.start_datetime = QDateTime.fromString(start_time_str, "yyyy-MM-dd HH:mm:ss")
                            if self.start_datetime.isValid():
                                # 如果是旧格式,将秒设为0
                                self.start_datetime.setTime(QTime(self.start_datetime.time().hour(), 
                                                                 self.start_datetime.time().minute(), 0))
                    if end_time_str:
                        # 先尝试新格式(精确到分钟)
                        self.end_datetime = QDateTime.fromString(end_time_str, "yyyy-MM-dd HH:mm")
                        if not self.end_datetime.isValid():
                            # 尝试旧格式(精确到秒)
                            self.end_datetime = QDateTime.fromString(end_time_str, "yyyy-MM-dd HH:mm:ss")
                            if self.end_datetime.isValid():
                                # 如果是旧格式,将秒设为0
                                self.end_datetime.setTime(QTime(self.end_datetime.time().hour(), 
                                                               self.end_datetime.time().minute(), 0))
                    
                    # 加载其他设置
                    self.loop_type = settings.get("loop_type", 0)
                    self.loop_count = settings.get("loop_count", 1000)
                    self.offset_enabled = settings.get("offset_enabled", False)
                    self.offset_x = settings.get("offset_x", 5)
                    self.offset_y = settings.get("offset_y", 5)
                    self.random_delay_enabled = settings.get("random_delay_enabled", False)
                    self.random_delay_min = settings.get("random_delay_min", 100)
                    self.random_delay_max = settings.get("random_delay_max", 1000)
                    
                    print(f"已从 {self.settings_file} 加载设置")
        except Exception as e:
            print(f"加载全局设置失败: {e}")
            self.hotkey = "F12"
            self.timed_execution_enabled = False

    def save_global_settings(self):
        """保存全局设置"""
        settings = {
            "hotkey": self.hotkey,
            "loop_type": self.loop_type,
            "loop_count": self.loop_count,
            "offset_enabled": self.offset_enabled,
            "offset_x": self.offset_x,
            "offset_y": self.offset_y,
            "random_delay_enabled": self.random_delay_enabled,
            "random_delay_min": self.random_delay_min,
            "random_delay_max": self.random_delay_max,
            "window_width": self.window_width,
            "window_height": self.window_height
        }
        try:
            with open(self.settings_file, 'w', encoding='utf-8') as f:
                json.dump(settings, f, indent=2, ensure_ascii=False)
            print(f"全局设置已保存到: {self.settings_file}")
        except Exception as e:
            print(f"保存全局设置失败: {e}")

    def set_controls_enabled(self, enabled):
        controls = [
            self.start_btn, self.stop_btn, self.pause_btn,
            self.save_btn, self.load_btn,
            self.timed_execution_checkbox,
            self.start_datetime_edit, self.end_datetime_edit,
            self.select_window_btn, self.clear_window_btn,
            self.record_hotkey_btn, self.other_settings_btn,
            self.steps_table, self.add_image_btn,
            self.add_coordinate_btn, self.add_keyboard_btn, self.add_text_btn,
            self.add_drag_btn, self.delete_btn, self.move_up_btn, 
            self.move_down_btn
        ]
        for control in controls:
            control.setEnabled(enabled)

    def setup_global_hotkey(self):
        try:
            if self.global_hotkey_listener:
                self.global_hotkey_listener.stop()
            keys = self.parse_hotkey_string(self.hotkey)
            # 修复拼写错误:global_hotkey_listner -> global_hotkey_listener
            self.global_hotkey_listener = KeyboardListener(
                on_press=lambda key: self.on_global_key_press(key, keys)
            )
            self.global_hotkey_listener.start()
            print(f"全局热键已设置: {self.hotkey}")
        except Exception as e:
            print(f"全局热键设置失败: {str(e)}")

    def parse_hotkey_string(self, hotkey_str):
        keys = []
        parts = hotkey_str.lower().split('+')
        for part in parts:
            part = part.strip()
            if part == 'ctrl':
                keys.append(Key.ctrl)
            elif part == 'alt':
                keys.append(Key.alt)
            elif part == 'shift':
                keys.append(Key.shift)
            elif part.startswith('f') and part[1:].isdigit():
                keys.append(getattr(Key, f'f{part[1:]}'))
            elif len(part) == 1:
                keys.append(part)
        return keys

    def on_global_key_press(self, key, target_keys):
        try:
            if isinstance(target_keys[-1], str):
                if hasattr(key, 'char') and key.char and key.char.lower() == target_keys[-1].lower():
                    if all(
                        self.keyboard.ctrl_pressed if m == Key.ctrl else
                        self.keyboard.alt_pressed if m == Key.alt else
                        self.keyboard.shift_pressed
                        for m in target_keys[:-1]
                    ):
                        QTimer.singleShot(0, self.toggle_execution)
                        return True
            else:
                if key == target_keys[-1]:
                    if all(
                        self.keyboard.ctrl_pressed if m == Key.ctrl else
                        self.keyboard.alt_pressed if m == Key.alt else
                        self.keyboard.shift_pressed
                        for m in target_keys[:-1]
                    ):
                        QTimer.singleShot(0, self.toggle_execution)
                        return True
        except:
            pass
        return True

    def start_select_window_mode(self):
        self.is_selecting_window = True
        self.setWindowState(Qt.WindowMinimized)

        self.preview_label.setText("请将鼠标移动到目标窗口上并点击左键...\n按ESC取消")
        self.preview_label.setStyleSheet(
            f"border: 1px solid #ccc; background-color: #fff3cd; color: #856404; font-size: {self.scaled_value(11)}px;"
        )

        self.mouse_listener = MouseListener(on_click=self.on_select_window_click)
        self.mouse_listener.start()
        self.keyboard_listener = KeyboardListener(on_press=self.on_select_window_cancel)
        self.keyboard_listener.start()

    def on_select_window_click(self, x, y, button, pressed):
        if pressed and button == Button.left:
            hwnd = win32gui.WindowFromPoint((x, y))
            while win32gui.GetParent(hwnd) != 0:
                hwnd = win32gui.GetParent(hwnd)
            self.target_hwnd = hwnd
            window_title = win32gui.GetWindowText(hwnd)
            self.window_title_edit.setText(f"{window_title} (HWND: {hwnd})")
            self.stop_select_window_mode_signal.emit()
            return False
        return True

    def on_select_window_cancel(self, key):
        if key == Key.esc:
            self.stop_select_window_mode_signal.emit()
            return False
        return True

    def stop_select_window_mode(self):
        self.is_selecting_window = False
        if self.mouse_listener:
            self.mouse_listener.stop()
            self.mouse_listener = None
        if self.keyboard_listener:
            self.keyboard_listener.stop()
            self.keyboard_listener = None

        self.showNormal()
        self.activateWindow()
        self.preview_label.setText("预览区域")
        self.preview_label.setStyleSheet(
            f"border: 1px solid #ccc; background-color: #f9f9f9; font-size: {self.scaled_value(11)}px; padding: {self.scaled_value(5)}px;"
        )

    def start_add_coordinate_mode(self):
        self.is_adding_coordinate = True
        self.setWindowState(Qt.WindowMinimized)

        self.preview_label.setText("请在屏幕任意位置点击要添加的坐标...\n按ESC或右键取消")
        self.preview_label.setStyleSheet(
            f"border: 1px solid #ccc; background-color: #fff3cd; color: #856404; font-size: {self.scaled_value(11)}px;"
        )

        self.mouse_listener = MouseListener(on_click=self.on_global_mouse_click)
        self.mouse_listener.start()
        self.keyboard_listener = KeyboardListener(on_press=self.on_global_key_press_for_cancel)
        self.keyboard_listener.start()

    def on_global_mouse_click(self, x, y, button, pressed):
        if pressed:
            if button == Button.left:
                self.coordinate_captured.emit(x, y)
                return False
            elif button == Button.right:
                self.stop_coordinate_mode.emit()
                return False
        return True

    def on_global_key_press_for_cancel(self, key):
        if key == Key.esc:
            self.stop_coordinate_mode.emit()
            return False
        return True

    def stop_add_coordinate_mode(self):
        self.is_adding_coordinate = False
        if self.mouse_listener:
            self.mouse_listener.stop()
            self.mouse_listener = None
        if self.keyboard_listener:
            self.keyboard_listener.stop()
            self.keyboard_listener = None

        self.showNormal()
        self.activateWindow()
        self.preview_label.setText("预览区域")
        self.preview_label.setStyleSheet(
            f"border: 1px solid #ccc; background-color: #f9f9f9; font-size: {self.scaled_value(11)}px; padding: {self.scaled_value(5)}px;"
        )

    def start_add_drag_mode(self):
        """开始添加鼠标拖动步骤"""
        self.is_adding_drag = True
        self.drag_points = []
        self.setWindowState(Qt.WindowMinimized)

        self.preview_label.setText("请点击第一个坐标点(起点)...\n按ESC或右键取消")
        self.preview_label.setStyleSheet(
            f"border: 1px solid #ccc; background-color: #fff3cd; color: #856404; font-size: {self.scaled_value(11)}px;"
        )

        self.mouse_listener = MouseListener(on_click=self.on_drag_mouse_click)
        self.mouse_listener.start()
        self.keyboard_listener = KeyboardListener(on_press=self.on_global_key_press_for_cancel)
        self.keyboard_listener.start()

    def on_drag_mouse_click(self, x, y, button, pressed):
        if pressed:
            if button == Button.left:
                self.drag_points.append((x, y))
                if len(self.drag_points) == 1:
                    self.preview_label.setText("请点击第二个坐标点(终点)...")
                elif len(self.drag_points) == 2:
                    self.drag_coordinate_captured.emit(x, y)
                    return False
            elif button == Button.right:
                self.stop_add_drag_mode()
                return False
        return True

    def on_drag_coordinate_captured(self, x, y):
        """处理鼠标拖动坐标采集完成"""
        start_x, start_y = self.drag_points[0]
        end_x, end_y = self.drag_points[1]
        
        # 转换为相对于目标窗口的坐标
        if self.target_hwnd:
            left, top, _, _ = win32gui.GetWindowRect(self.target_hwnd)
            start_x -= left
            start_y -= top
            end_x -= left
            end_y -= top

        # 显示拖动设置对话框
        dialog = DragSettingsDialog(self, (start_x, start_y), (end_x, end_y))
        if dialog.exec_() == QDialog.Accepted:
            step = Step(
                step_type='drag',
                drag_start_x=start_x,
                drag_start_y=start_y,
                drag_end_x=end_x,
                drag_end_y=end_y,
                click_type=dialog.get_drag_type(),
                wait_time=1000,
                random_delay_enabled=self.random_delay_enabled,
                drag_start_delay=dialog.get_start_delay(),
                drag_duration=dialog.get_duration()
            )
            self.steps.append(step)
            self.update_steps_table()
        
        self.stop_add_drag_mode()

    def stop_add_drag_mode(self):
        """停止添加鼠标拖动步骤"""
        self.is_adding_drag = False
        self.drag_points = []
        if self.mouse_listener:
            self.mouse_listener.stop()
            self.mouse_listener = None
        if self.keyboard_listener:
            self.keyboard_listener.stop()
            self.keyboard_listener = None

        self.showNormal()
        self.activateWindow()
        self.preview_label.setText("预览区域")
        self.preview_label.setStyleSheet(
            f"border: 1px solid #ccc; background-color: #f9f9f9; font-size: {self.scaled_value(11)}px; padding: {self.scaled_value(5)}px;"
        )

    def add_image_step(self):
        self.is_adding_image = True
        self.setWindowState(Qt.WindowMinimized)

        self.screenshot_window = ScreenshotWindow(self)
        self.screenshot_window.image_captured.connect(self.on_image_captured)
        self.screenshot_window.closed.connect(self.on_screenshot_closed)

        self.screenshot_window.setWindowFlags(
            self.screenshot_window.windowFlags()
            | Qt.WindowStaysOnTopHint
            | Qt.Tool
        )
        self.screenshot_window.show()
        self.screenshot_window.raise_()
        self.screenshot_window.activateWindow()

    def add_text_step(self):
        """添加文字输入步骤"""
        dialog = TextInputDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            text = dialog.get_text()
            if text:
                step = Step(
                    step_type='text',
                    text_content=text,
                    wait_time=1000,
                    click_type='press',
                    random_delay_enabled=self.random_delay_enabled
                )
                self.steps.append(step)
                self.update_steps_table()

    def add_keyboard_step(self):
        """添加键盘步骤"""
        dialog = KeyboardInputDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            key_sequence = dialog.get_key_sequence()
            if key_sequence:
                step = Step(
                    step_type='keyboard',
                    key_sequence=key_sequence,
                    wait_time=1000,
                    click_type='press',
                    random_delay_enabled=self.random_delay_enabled,
                    key_action_type=dialog.get_action_type(),
                    key_hold_time=dialog.get_hold_time(),
                    key_hold_image_path=dialog.get_hold_image_path()
                )
                self.steps.append(step)
                self.update_steps_table()

    def on_keyboard_image_selected(self, image_path):
        """处理键盘对话框中的图像选择"""
        # 发送信号给键盘对话框
        if hasattr(self, 'current_keyboard_dialog'):
            self.current_keyboard_dialog.set_captured_image(image_path)

    def on_screenshot_closed(self):
        self.is_adding_image = False
        self.showNormal()
        self.activateWindow()

    def on_coordinate_captured(self, x, y):
        rel_x, rel_y = x, y
        if self.target_hwnd:
            left, top, _, _ = win32gui.GetWindowRect(self.target_hwnd)
            rel_x -= left
            rel_y -= top

        step = Step(
            step_type='coordinate',
            x=rel_x,
            y=rel_y,
            wait_time=1000,
            click_type='left',
            random_delay_enabled=self.random_delay_enabled
        )
        self.steps.append(step)
        self.update_steps_table()
        self.stop_add_coordinate_mode()

    def toggle_offset(self, state):
        enabled = state == Qt.Checked
        self.offset_x_spin.setEnabled(enabled)
        self.offset_y_spin.setEnabled(enabled)

    def toggle_random_delay(self, state):
        enabled = state == Qt.Checked
        self.random_delay_min_spin.setEnabled(enabled)
        self.random_delay_max_spin.setEnabled(enabled)

    def toggle_execution(self):
        self.update_all_settings()
        # 热键启动同步定时时间
        if self.timed_execution_checkbox.isChecked():
            self.start_datetime = self.start_datetime_edit.dateTime()
            self.end_datetime = self.end_datetime_edit.dateTime()
        if self.is_running:
            self.stop_execution()
        else:
            self.start_execution()

    def get_loop_count(self):
        return 999999 if self.loop_type == 0 else self.loop_count

    def start_execution(self):
        if not self.steps:
            QMessageBox.warning(self, "错误", "没有可执行的步骤")
            return
        # 每次启动强制同步界面最新定时时间
        if self.timed_execution_checkbox.isChecked():
            start_dt = self.start_datetime_edit.dateTime()
            end_dt = self.end_datetime_edit.dateTime()
            if start_dt >= end_dt:
                QMessageBox.warning(self, "错误", "开始时间必须早于结束时间")
                return
            current_dt = QDateTime.currentDateTime()
            if current_dt > end_dt:
                QMessageBox.warning(self, "错误", "结束时间已过,请重新设置")
                return
            # 强制刷新全局定时变量,线程实时读取
            self.start_datetime = start_dt
            self.end_datetime = end_dt
        else:
            self.start_datetime = None
            self.end_datetime = None

        if self.target_hwnd and not ctypes.windll.shell32.IsUserAnAdmin():
            reply = QMessageBox.question(
                self, '权限提示',
                '检测到您选择了指定窗口,为保证后台点击生效,建议以管理员身份运行此程序。\n是否继续?',
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No
            )
            if reply == QMessageBox.No:
                return
        self.update_all_settings()
        if self.is_paused:
            self.is_paused = False
            self.is_running = True
            self.execution_thread.resume()
            self.pause_btn.setText("&#9208; 暂停执行")
        else:
            self.current_step_index = 0
            self.is_running = True
            self.is_paused = False
            loop_count = self.get_loop_count()
            self.execution_thread.set_params(loop_count, self.current_step_index)
            self.execution_thread.start()
        self.set_controls_enabled(False)
        self.start_btn.setEnabled(False)
        self.stop_btn.setEnabled(True)
        self.pause_btn.setEnabled(True)

    def stop_execution(self):
        self.is_running = False
        self.is_paused = False
        self.current_step_index = 0
        self.execution_thread.stop()
        self.set_controls_enabled(True)
        self.start_btn.setEnabled(True)
        self.stop_btn.setEnabled(False)
        self.pause_btn.setEnabled(False)
        self.pause_btn.setText("&#9208; 暂停执行")

    def pause_execution(self):
        if self.is_running and not self.is_paused:
            self.is_paused = True
            self.is_running = False
            self.execution_thread.pause()
            self.start_btn.setEnabled(True)
            self.pause_btn.setText("&#9654; 继续执行")
        elif self.is_paused:
            self.update_all_settings()
            self.is_paused = False
            self.is_running = True
            self.execution_thread.resume()
            self.start_btn.setEnabled(False)
            self.pause_btn.setText("&#9208; 暂停执行")

    def update_all_settings(self):
        for step in self.steps:
            step.random_delay_enabled = self.random_delay_enabled
            step.random_delay_min = self.random_delay_min
            step.random_delay_max = self.random_delay_max

    def clear_target_window(self):
        self.target_hwnd = None
        self.window_title_edit.clear()
        QMessageBox.information(self, "提示", "已清除目标窗口")

    def on_execution_finished(self):
        self.is_running = False
        self.is_paused = False
        self.current_step_index = 0
        self.set_controls_enabled(True)
        self.start_btn.setEnabled(True)
        self.stop_btn.setEnabled(False)
        self.pause_btn.setEnabled(False)
        self.pause_btn.setText("&#9208; 暂停执行")

    def on_execution_error(self, error_msg):
        self.is_running = False
        self.is_paused = False
        self.current_step_index = 0
        self.set_controls_enabled(True)
        self.start_btn.setEnabled(True)
        self.stop_btn.setEnabled(False)
        self.pause_btn.setEnabled(False)
        self.pause_btn.setText("&#9208; 暂停执行")
        QMessageBox.critical(self, "错误", f"执行出错: {error_msg}")

    def on_step_completed(self, step_index):
        self.current_step_index = step_index
        self.steps_table.selectRow(step_index)

    def on_image_captured(self, image_path):
        # 将截图保存到screenshots目录
        filename = os.path.basename(image_path)
        new_path = os.path.join(self.screenshot_dir, filename)
        shutil.move(image_path, new_path)
        image_path = new_path

        if self.target_hwnd:
            left, top, right, bottom = win32gui.GetWindowRect(self.target_hwnd)
            bbox = (left, top, right, bottom)
            screen_image = ImageGrab.grab(bbox)
            template_path = os.path.join(self.screenshot_dir, f"template_{int(time.time())}.png")
            screen_image.save(template_path)
            image_path = template_path

        step = Step(
            step_type='image',
            image_path=image_path,
            wait_time=1000,
            similarity=0.8,
            click_type='left',
            random_delay_enabled=self.random_delay_enabled
        )
        self.steps.append(step)
        self.update_steps_table()

    def delete_step(self):
        current_row = self.steps_table.currentRow()
        if current_row >= 0:
            del self.steps[current_row]
            self.update_steps_table()

    def move_step_up(self):
        current_row = self.steps_table.currentRow()
        if current_row > 0:
            self.steps[current_row], self.steps[current_row - 1] = \
                self.steps[current_row - 1], self.steps[current_row]
            self.update_steps_table()
            self.steps_table.selectRow(current_row - 1)

    def move_step_down(self):
        current_row = self.steps_table.currentRow()
        if current_row < len(self.steps) - 1:
            self.steps[current_row], self.steps[current_row + 1] = \
                self.steps[current_row + 1], self.steps[current_row]
            self.update_steps_table()
            self.steps_table.selectRow(current_row + 1)

    def update_steps_table(self):
        self.steps_table.setRowCount(len(self.steps))

        for i, step in enumerate(self.steps):
            step_item = QTableWidgetItem(f"{i + 1}")
            step_item.setTextAlignment(Qt.AlignCenter)
            self.steps_table.setItem(i, 0, step_item)

            # 类型列
            if step.step_type == 'image':
                type_text = "图像识别"
            elif step.step_type == 'coordinate':
                type_text = "坐标点击"
            elif step.step_type == 'keyboard':
                action_types = ["按键一次", "长按指定时间", "按住直到图像出现"]
                action_text = action_types[step.key_action_type]
                
                if step.key_action_type == 1:  # 长按指定时间
                    action_text += f"({step.key_hold_time}ms)"
                elif step.key_action_type == 2:  # 按住直到图像出现
                    if step.key_hold_image_path and os.path.exists(step.key_hold_image_path):
                        img_name = os.path.basename(step.key_hold_image_path)
                        action_text += f"(等待:{img_name})"
                    else:
                        action_text += "(等待:未设置图像)"
                
                type_text = f"按键: {step.key_sequence} [{action_text}]"
            elif step.step_type == 'text':
                type_text = "文字输入"
            elif step.step_type == 'drag':
                delay_info = ""
                if step.drag_start_delay > 0:
                    delay_info += f"延迟{step.drag_start_delay}ms"
                if step.drag_duration > 0:
                    delay_info += f" 持续{step.drag_duration}ms"
                type_text = f"鼠标拖动{delay_info}"
            else:
                type_text = "未知"
            type_item = QTableWidgetItem(type_text)
            type_item.setTextAlignment(Qt.AlignCenter)
            self.steps_table.setItem(i, 1, type_item)

            # 点击类型列
            click_combo = QComboBox()
            if step.step_type == 'keyboard':
                click_combo.addItem("按键一次")
                click_combo.setEnabled(False)
            elif step.step_type == 'text':
                click_combo.addItem("输入文字")
                click_combo.setEnabled(False)
            elif step.step_type == 'drag':
                click_combo.addItems(["左键拖动", "右键拖动"])
                if step.click_type == 'left':
                    click_combo.setCurrentIndex(0)
                else:
                    click_combo.setCurrentIndex(1)
            else:
                click_combo.addItems(["左键单击", "右键单击", "双击"])
                if step.click_type == 'left':
                    click_combo.setCurrentIndex(0)
                elif step.click_type == 'right':
                    click_combo.setCurrentIndex(1)
                elif step.click_type == 'double':
                    click_combo.setCurrentIndex(2)
            click_combo.currentTextChanged.connect(
                lambda text, row=i: self.update_step_click_type(row, text)
            )
            self.steps_table.setCellWidget(i, 2, click_combo)

            # 等待时间列
            wait_spin = QSpinBox()
            wait_spin.setRange(0, 3600000)
            wait_spin.setValue(step.wait_time)
            wait_spin.valueChanged.connect(
                lambda value, row=i: self.update_step_wait_time(row, value)
            )
            self.steps_table.setCellWidget(i, 3, wait_spin)

            # 相似度列(仅图像步骤)
            if step.step_type == 'image':
                similarity_spin = QDoubleSpinBox()
                similarity_spin.setRange(0.1, 1.0)
                similarity_spin.setSingleStep(0.05)
                similarity_spin.setValue(step.similarity)
                similarity_spin.setDecimals(2)
                similarity_spin.valueChanged.connect(
                    lambda value, row=i: self.update_step_similarity(row, value)
                )
                self.steps_table.setCellWidget(i, 4, similarity_spin)
            else:
                similarity_item = QTableWidgetItem("-")
                similarity_item.setTextAlignment(Qt.AlignCenter)
                self.steps_table.setItem(i, 4, similarity_item)

            # 接受偏移列
            offset_checkbox = QCheckBox()
            offset_checkbox.setStyleSheet("QCheckBox { margin-left: auto; margin-right: auto; }")
            offset_checkbox.setChecked(step.accept_offset)
            offset_checkbox.setEnabled(step.step_type not in ['keyboard', 'text', 'drag'])
            offset_checkbox.stateChanged.connect(
                lambda state, row=i: self.update_step_accept_offset(row, state)
            )
            self.steps_table.setCellWidget(i, 5, offset_checkbox)

            # 接受随机延迟列
            random_delay_checkbox = QCheckBox()
            random_delay_checkbox.setStyleSheet("QCheckBox { margin-left: auto; margin-right: auto; }")
            random_delay_checkbox.setChecked(step.accept_random_delay)
            random_delay_checkbox.stateChanged.connect(
                lambda state, row=i: self.update_step_accept_random_delay(row, state)
            )
            self.steps_table.setCellWidget(i, 6, random_delay_checkbox)

            # 找到跳转列(仅图像步骤)
            found_combo = QComboBox()
            found_combo.addItem("无跳转", -1)
            for j in range(len(self.steps)):
                found_combo.addItem(f"步骤 {j + 1}", j)
            if step.jump_on_found is not None and step.jump_on_found < len(self.steps):
                found_combo.setCurrentIndex(step.jump_on_found + 1)
            found_combo.setEnabled(step.step_type == 'image')
            found_combo.currentIndexChanged.connect(
                lambda index, row=i: self.update_step_jump_on_found(row, index)
            )
            self.steps_table.setCellWidget(i, 7, found_combo)

            # 未找到跳转列(仅图像步骤)
            not_found_combo = QComboBox()
            not_found_combo.addItem("无跳转", -1)
            for j in range(len(self.steps)):
                not_found_combo.addItem(f"步骤 {j + 1}", j)
            if step.jump_on_not_found is not None and step.jump_on_not_found < len(self.steps):
                not_found_combo.setCurrentIndex(step.jump_on_not_found + 1)
            not_found_combo.setEnabled(step.step_type == 'image')
            not_found_combo.currentIndexChanged.connect(
                lambda index, row=i: self.update_step_jump_on_not_found(row, index)
            )
            self.steps_table.setCellWidget(i, 8, not_found_combo)

    def update_step_jump_on_found(self, row, index):
        if row < len(self.steps):
            combo = self.steps_table.cellWidget(row, 7)
            if combo:
                jump_to = combo.itemData(index)
                self.steps[row].jump_on_found = jump_to if jump_to != -1 else None

    def update_step_jump_on_not_found(self, row, index):
        if row < len(self.steps):
            combo = self.steps_table.cellWidget(row, 8)
            if combo:
                jump_to = combo.itemData(index)
                self.steps[row].jump_on_not_found = jump_to if jump_to != -1 else None

    def update_step_accept_offset(self, row, state):
        if row < len(self.steps):
            self.steps[row].accept_offset = (state == Qt.Checked)

    def update_step_accept_random_delay(self, row, state):
        if row < len(self.steps):
            self.steps[row].accept_random_delay = (state == Qt.Checked)

    def update_step_click_type(self, row, text):
        if row < len(self.steps):
            if text == "左键单击":
                self.steps[row].click_type = 'left'
            elif text == "右键单击":
                self.steps[row].click_type = 'right'
            elif text == "双击":
                self.steps[row].click_type = 'double'
            elif text == "左键拖动":
                self.steps[row].click_type = 'left'
            elif text == "右键拖动":
                self.steps[row].click_type = 'right'

    def update_step_wait_time(self, row, value):
        if row < len(self.steps):
            self.steps[row].wait_time = value

    def update_step_similarity(self, row, value):
        if row < len(self.steps):
            self.steps[row].similarity = value

    def on_step_selected_from_table(self, row, column):
        if row < len(self.steps):
            step = self.steps[row]
            if step.step_type == 'image' and step.image_path and os.path.exists(step.image_path):
                pixmap = QPixmap(step.image_path)
                self.preview_label.setPixmap(pixmap.scaled(
                    self.scaled_value(300), 
                    self.scaled_value(150), 
                    Qt.KeepAspectRatio
                ))
            elif step.step_type == 'coordinate':
                self.preview_label.setText(f"坐标: ({step.x}, {step.y})")
            elif step.step_type == 'keyboard':
                action_types = ["按键一次", "长按指定时间", "按住直到图像出现"]
                action_text = action_types[step.key_action_type]
                
                detail_text = f"按键: {step.key_sequence}\n操作类型: {action_text}"
                
                if step.key_action_type == 1:  # 长按指定时间
                    detail_text += f"\n按住时间: {step.key_hold_time}ms"
                elif step.key_action_type == 2:  # 按住直到图像出现
                    if step.key_hold_image_path and os.path.exists(step.key_hold_image_path):
                        img_name = os.path.basename(step.key_hold_image_path)
                        detail_text += f"\n等待图像: {img_name}"
                    else:
                        detail_text += "\n等待图像: 未设置"
                
                self.preview_label.setText(detail_text)
            elif step.step_type == 'text':
                preview_text = step.text_content[:50] + "..." if len(step.text_content) > 50 else step.text_content
                self.preview_label.setText(f"文字: {preview_text}")
            elif step.step_type == 'drag':
                delay_info = ""
                if step.drag_start_delay > 0:
                    delay_info += f"\n开始延迟: {step.drag_start_delay}ms"
                if step.drag_duration > 0:
                    delay_info += f"\n拖动持续: {step.drag_duration}ms"
                self.preview_label.setText(f"拖动: ({step.drag_start_x},{step.drag_start_y}) → ({step.drag_end_x},{step.drag_end_y}){delay_info}")

    def save_config(self):
        file_path, _ = QFileDialog.getSaveFileName(self, "保存配置", "", "JSON文件 (*.json)")
        if not file_path:
            return

        try:
            config = {
                "steps": [],
                "loop_type": self.loop_type,
                "loop_count": self.loop_count,
                "offset_enabled": self.offset_enabled,
                "offset_x": self.offset_x,
                "offset_y": self.offset_y,
                "random_delay_enabled": self.random_delay_enabled,
                "random_delay_min": self.random_delay_min,
                "random_delay_max": self.random_delay_max,
            }

            config_dir = os.path.dirname(file_path)
            screenshots_dir = os.path.join(config_dir, "screenshots")
            
            # 确保截图目录存在
            os.makedirs(screenshots_dir, exist_ok=True)

            for i, step in enumerate(self.steps):
                step_data = {
                    "step_type": step.step_type,
                    "image_path": step.image_path,
                    "x": step.x,
                    "y": step.y,
                    "click_type": step.click_type,
                    "wait_time": step.wait_time,
                    "similarity": step.similarity,
                    "random_delay_enabled": step.random_delay_enabled,
                    "random_delay_min": step.random_delay_min,
                    "random_delay_max": step.random_delay_max,
                    "accept_offset": step.accept_offset,
                    "accept_random_delay": step.accept_random_delay,
                    "jump_on_found": step.jump_on_found,
                    "jump_on_not_found": step.jump_on_not_found,
                    "key_sequence": step.key_sequence,
                    "text_content": step.text_content,
                    "drag_start_x": step.drag_start_x,
                    "drag_start_y": step.drag_start_y,
                    "drag_end_x": step.drag_end_x,
                    "drag_end_y": step.drag_end_y,
                    # 键盘操作相关字段
                    "key_action_type": step.key_action_type,
                    "key_hold_time": step.key_hold_time,
                    "key_hold_image_path": step.key_hold_image_path,
                    # 拖动缓冲相关字段
                    "drag_start_delay": step.drag_start_delay,
                    "drag_duration": step.drag_duration
                }

                if step.step_type == "image" and step.image_path:
                    src_path = step.image_path
                    if os.path.isabs(src_path):
                        # 使用安全的文件名
                        safe_filename = f"step_{i+1}_{int(time.time())}.png"
                        dst_path = os.path.join(screenshots_dir, safe_filename)
                        
                        # 确保源文件存在
                        if os.path.exists(src_path):
                            try:
                                # 使用copy2而不是move,避免源文件丢失
                                shutil.copy2(src_path, dst_path)
                                step_data["image_path"] = os.path.join("screenshots", safe_filename)
                            except Exception as e:
                                print(f"复制图像文件失败: {e}")
                                # 如果复制失败,使用相对路径
                                step_data["image_path"] = os.path.relpath(src_path, config_dir)
                        else:
                            print(f"警告: 图像文件不存在: {src_path}")
                            step_data["image_path"] = src_path
                
                # 处理键盘操作中使用的图像
                if step.step_type == "keyboard" and step.key_action_type == 2 and step.key_hold_image_path:
                    src_path = step.key_hold_image_path
                    if os.path.isabs(src_path):
                        safe_filename = f"keyboard_wait_{i+1}_{int(time.time())}.png"
                        dst_path = os.path.join(screenshots_dir, safe_filename)
                        
                        if os.path.exists(src_path):
                            try:
                                shutil.copy2(src_path, dst_path)
                                step_data["key_hold_image_path"] = os.path.join("screenshots", safe_filename)
                            except Exception as e:
                                print(f"复制键盘等待图像失败: {e}")
                                step_data["key_hold_image_path"] = src_path

                config["steps"].append(step_data)

            # 使用with语句确保文件正确关闭
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2, ensure_ascii=False)

            QMessageBox.information(self, "成功", f"配置已保存到:\n{file_path}")
            
        except Exception as e:
            QMessageBox.critical(self, "保存失败", f"保存配置时发生错误:\n{str(e)}")
            import traceback
            traceback.print_exc()

    def load_config(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "加载配置", "", "JSON文件 (*.json)")
        if not file_path:
            return

        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                config = json.load(f)

            self.steps.clear()
            self.current_step_index = 0
            self.is_running = False
            self.is_paused = False

            self.target_hwnd = None
            self.window_title_edit.clear()

            config_dir = os.path.dirname(file_path)

            for step_data in config["steps"]:
                if step_data["step_type"] == "image" and step_data["image_path"]:
                    path = step_data["image_path"]
                    
                    if os.path.isabs(path):
                        final_path = path
                    else:
                        config_path = os.path.join(config_dir, path)
                        base_path = os.path.join(self.app_base_dir, path)
                        base_screenshots_path = os.path.join(self.app_base_dir, "screenshots", os.path.basename(path))
                        config_screenshots_path = os.path.join(config_dir, "screenshots", os.path.basename(path))
                        
                        if os.path.exists(config_path):
                            final_path = config_path
                        elif os.path.exists(base_path):
                            final_path = base_path
                        elif os.path.exists(base_screenshots_path):
                            final_path = base_screenshots_path
                        elif os.path.exists(config_screenshots_path):
                            final_path = config_screenshots_path
                        else:
                            final_path = config_path
                            print(f"警告: 找不到图像文件: {path}")
                    
                    step_data["image_path"] = final_path
                
                # 处理键盘操作中使用的图像
                if step_data["step_type"] == "keyboard" and step_data.get("key_hold_image_path"):
                    path = step_data["key_hold_image_path"]
                    
                    if os.path.isabs(path):
                        final_path = path
                    else:
                        config_path = os.path.join(config_dir, path)
                        base_path = os.path.join(self.app_base_dir, path)
                        base_screenshots_path = os.path.join(self.app_base_dir, "screenshots", os.path.basename(path))
                        config_screenshots_path = os.path.join(config_dir, "screenshots", os.path.basename(path))
                        
                        if os.path.exists(config_path):
                            final_path = config_path
                        elif os.path.exists(base_path):
                            final_path = base_path
                        elif os.path.exists(base_screenshots_path):
                            final_path = base_screenshots_path
                        elif os.path.exists(config_screenshots_path):
                            final_path = config_screenshots_path
                        else:
                            final_path = config_path
                            print(f"警告: 找不到键盘等待图像文件: {path}")
                    
                    step_data["key_hold_image_path"] = final_path

                # 兼容新增字段
                step = Step(
                    step_type=step_data["step_type"],
                    image_path=step_data.get("image_path"),
                    x=step_data.get("x"),
                    y=step_data.get("y"),
                    click_type=step_data.get("click_type", "left"),
                    wait_time=step_data.get("wait_time", 1000),
                    similarity=step_data.get("similarity", 0.8),
                    random_delay_enabled=step_data.get("random_delay_enabled", False),
                    random_delay_min=step_data.get("random_delay_min", 100),
                    random_delay_max=step_data.get("random_delay_max", 1000),
                    accept_offset=step_data.get("accept_offset", True),
                    accept_random_delay=step_data.get("accept_random_delay", True),
                    jump_on_found=step_data.get("jump_on_found"),
                    jump_on_not_found=step_data.get("jump_on_not_found"),
                    key_sequence=step_data.get("key_sequence"),
                    text_content=step_data.get("text_content"),
                    drag_start_x=step_data.get("drag_start_x"),
                    drag_start_y=step_data.get("drag_start_y"),
                    drag_end_x=step_data.get("drag_end_x"),
                    drag_end_y=step_data.get("drag_end_y"),
                    # 新增字段的兼容处理
                    key_action_type=step_data.get("key_action_type", 0),
                    key_hold_time=step_data.get("key_hold_time", 0),
                    key_hold_image_path=step_data.get("key_hold_image_path"),
                    drag_start_delay=step_data.get("drag_start_delay", 0),
                    drag_duration=step_data.get("drag_duration", 1000)
                )
                self.steps.append(step)

            # 加载设置
            self.loop_type = config.get("loop_type", 0)
            self.loop_count = config.get("loop_count", 1000)
            self.offset_enabled = config.get("offset_enabled", False)
            self.offset_x = config.get("offset_x", 5)
            self.offset_y = config.get("offset_y", 5)
            self.random_delay_enabled = config.get("random_delay_enabled", False)
            self.random_delay_min = config.get("random_delay_min", 100)
            self.random_delay_max = config.get("random_delay_max", 1000)

            # 加载定时设置
            timed_enabled = config.get("timed_execution_enabled", False)
            self.timed_execution_checkbox.setChecked(timed_enabled)
            start_datetime_str = config.get("start_datetime", "")
            end_datetime_str = config.get("end_datetime", "")
            
            # 尝试解析时间字符串,支持新旧两种格式
            if start_datetime_str:
                self.start_datetime = QDateTime.fromString(start_datetime_str, "yyyy-MM-dd HH:mm")
                if not self.start_datetime.isValid():
                    # 尝试旧格式
                    self.start_datetime = QDateTime.fromString(start_datetime_str, "yyyy-MM-dd HH:mm:ss")
                    if self.start_datetime.isValid():
                        self.start_datetime.setTime(QTime(self.start_datetime.time().hour(), 
                                                         self.start_datetime.time().minute(), 0))
                if self.start_datetime.isValid():
                    self.start_datetime_edit.setDateTime(self.start_datetime)
            
            if end_datetime_str:
                self.end_datetime = QDateTime.fromString(end_datetime_str, "yyyy-MM-dd HH:mm")
                if not self.end_datetime.isValid():
                    # 尝试旧格式
                    self.end_datetime = QDateTime.fromString(end_datetime_str, "yyyy-MM-dd HH:mm:ss")
                    if self.end_datetime.isValid():
                        self.end_datetime.setTime(QTime(self.end_datetime.time().hour(), 
                                                       self.end_datetime.time().minute(), 0))
                if self.end_datetime.isValid():
                    self.end_datetime_edit.setDateTime(self.end_datetime)

            self.update_steps_table()
            QMessageBox.information(self, "成功", f"配置已从以下位置加载:\n{file_path}")
            
        except Exception as e:
            QMessageBox.critical(self, "加载失败", f"加载配置时发生错误:\n{str(e)}")
            import traceback
            traceback.print_exc()

    def closeEvent(self, event):
        self.stop_execution()
        if self.global_hotkey_listener:
            self.global_hotkey_listener.stop()
        if self.mouse_listener:
            self.mouse_listener.stop()
        if self.keyboard_listener:
            self.keyboard_listener.stop()
        if hasattr(self, 'hotkey_listener'):
            self.hotkey_listener.stop()
        self.save_global_settings()
        self.save_window_size()
        event.accept()


class OtherSettingsDialog(QDialog):
    """其他设置对话框"""
    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        self.setWindowTitle("其他设置")
        self.setModal(True)
        
        # 根据父窗口的缩放比例调整对话框尺寸
        self.setFixedSize(
            parent.scaled_value(400),
            parent.scaled_value(450)
        )
        
        layout = QVBoxLayout()
        layout.setSpacing(parent.scaled_value(10))
        layout.setContentsMargins(
            parent.scaled_value(15),
            parent.scaled_value(15),
            parent.scaled_value(15),
            parent.scaled_value(15)
        )
        
        # 循环设置组
        loop_group = QGroupBox("循环设置")
        loop_layout = QVBoxLayout()
        loop_layout.setSpacing(parent.scaled_value(5))
        
        self.loop_type_group = QButtonGroup()
        self.radio_infinite = QRadioButton("无限循环")
        self.radio_count = QRadioButton("次数循环")
        self.radio_infinite.setChecked(parent.loop_type == 0)
        self.radio_count.setChecked(parent.loop_type == 1)
        self.loop_type_group.addButton(self.radio_infinite, 0)
        self.loop_type_group.addButton(self.radio_count, 1)
        
        loop_layout.addWidget(self.radio_infinite)
        loop_layout.addWidget(self.radio_count)
        
        count_layout = QHBoxLayout()
        count_layout.addWidget(QLabel("次数:"))
        self.loop_spin = QSpinBox()
        self.loop_spin.setRange(1, 99999)
        self.loop_spin.setValue(parent.loop_count)
        self.loop_spin.setMinimumHeight(parent.scaled_value(30))
        count_layout.addWidget(self.loop_spin)
        loop_layout.addLayout(count_layout)
        
        loop_group.setLayout(loop_layout)
        layout.addWidget(loop_group)
        
        # 偏移设置组
        offset_group = QGroupBox("偏移设置(X轴和Y轴点击偏移量)")
        offset_layout = QVBoxLayout()
        offset_layout.setSpacing(parent.scaled_value(5))
        
        self.offset_checkbox = QCheckBox("启用偏移")
        self.offset_checkbox.setChecked(parent.offset_enabled)
        self.offset_checkbox.stateChanged.connect(self.toggle_offset)
        
        x_layout = QHBoxLayout()
        x_layout.addWidget(QLabel("X轴偏移:"))
        self.offset_x_spin = QSpinBox()
        self.offset_x_spin.setRange(0, 50)
        self.offset_x_spin.setValue(parent.offset_x)
        self.offset_x_spin.setEnabled(parent.offset_enabled)
        self.offset_x_spin.setMinimumHeight(parent.scaled_value(30))
        x_layout.addWidget(self.offset_x_spin)
        
        y_layout = QHBoxLayout()
        y_layout.addWidget(QLabel("Y轴偏移:"))
        self.offset_y_spin = QSpinBox()
        self.offset_y_spin.setRange(0, 50)
        self.offset_y_spin.setValue(parent.offset_y)
        self.offset_y_spin.setEnabled(parent.offset_enabled)
        self.offset_y_spin.setMinimumHeight(parent.scaled_value(30))
        y_layout.addWidget(self.offset_y_spin)
        
        offset_layout.addWidget(self.offset_checkbox)
        offset_layout.addLayout(x_layout)
        offset_layout.addLayout(y_layout)
        offset_group.setLayout(offset_layout)
        layout.addWidget(offset_group)
        
        # 随机延迟设置组
        random_delay_group = QGroupBox("启动随机延迟(1秒等于1000毫秒)")
        random_delay_layout = QVBoxLayout()
        random_delay_layout.setSpacing(parent.scaled_value(5))
        
        self.random_delay_checkbox = QCheckBox("启动随机延迟")
        self.random_delay_checkbox.setChecked(parent.random_delay_enabled)
        self.random_delay_checkbox.stateChanged.connect(self.toggle_random_delay)
        
        min_layout = QHBoxLayout()
        min_layout.addWidget(QLabel("最低值(毫秒):"))
        self.random_delay_min_spin = QSpinBox()
        self.random_delay_min_spin.setRange(0, 10000)
        self.random_delay_min_spin.setValue(parent.random_delay_min)
        self.random_delay_min_spin.setEnabled(parent.random_delay_enabled)
        self.random_delay_min_spin.setMinimumHeight(parent.scaled_value(30))
        min_layout.addWidget(self.random_delay_min_spin)
        
        max_layout = QHBoxLayout()
        max_layout.addWidget(QLabel("最高值(毫秒):"))
        self.random_delay_max_spin = QSpinBox()
        self.random_delay_max_spin.setRange(0, 10000)
        self.random_delay_max_spin.setValue(parent.random_delay_max)
        self.random_delay_max_spin.setEnabled(parent.random_delay_enabled)
        self.random_delay_max_spin.setMinimumHeight(parent.scaled_value(30))
        max_layout.addWidget(self.random_delay_max_spin)
        
        random_delay_layout.addWidget(self.random_delay_checkbox)
        random_delay_layout.addLayout(min_layout)
        random_delay_layout.addLayout(max_layout)
        random_delay_group.setLayout(random_delay_layout)
        layout.addWidget(random_delay_group)
        
        # 按钮
        button_layout = QHBoxLayout()
        button_layout.setSpacing(parent.scaled_value(10))
        ok_button = QPushButton("确定")
        ok_button.setMinimumHeight(parent.scaled_value(35))
        ok_button.clicked.connect(self.apply_settings)
        cancel_button = QPushButton("取消")
        cancel_button.setMinimumHeight(parent.scaled_value(35))
        cancel_button.clicked.connect(self.reject)
        button_layout.addWidget(ok_button)
        button_layout.addWidget(cancel_button)
        layout.addLayout(button_layout)
        
        self.setLayout(layout)
    
    def toggle_offset(self, state):
        enabled = state == Qt.Checked
        self.offset_x_spin.setEnabled(enabled)
        self.offset_y_spin.setEnabled(enabled)
    
    def toggle_random_delay(self, state):
        enabled = state == Qt.Checked
        self.random_delay_min_spin.setEnabled(enabled)
        self.random_delay_max_spin.setEnabled(enabled)
    
    def apply_settings(self):
        """应用设置到主窗口"""
        self.parent.loop_type = 0 if self.radio_infinite.isChecked() else 1
        self.parent.loop_count = self.loop_spin.value()
        self.parent.offset_enabled = self.offset_checkbox.isChecked()
        self.parent.offset_x = self.offset_x_spin.value()
        self.parent.offset_y = self.offset_y_spin.value()
        self.parent.random_delay_enabled = self.random_delay_checkbox.isChecked()
        self.parent.random_delay_min = self.random_delay_min_spin.value()
        self.parent.random_delay_max = self.random_delay_max_spin.value()
        
        self.accept()
        QMessageBox.information(self.parent, "成功", "设置已保存!")


class ScreenshotWindow(QWidget):
    image_captured = pyqtSignal(str)
    closed = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__()
        self.parent = parent
        self.setWindowTitle("截图")
        self.setWindowState(Qt.WindowFullScreen)
        self.setWindowOpacity(0.3)
        self.setMouseTracking(True)

        self.setWindowFlags(
            Qt.FramelessWindowHint |
            Qt.WindowStaysOnTopHint |
            Qt.Tool
        )

        self.start_pos = None
        self.end_pos = None
        self.is_selecting = False

        self.shortcut_esc = QShortcut(QKeySequence(Qt.Key_Escape), self)
        self.shortcut_esc.activated.connect(self.cancel_screenshot)

    def cancel_screenshot(self):
        self.close()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.start_pos = event.pos()
            self.is_selecting = True
        elif event.button() == Qt.RightButton:
            self.cancel_screenshot()

    def mouseMoveEvent(self, event):
        if self.is_selecting:
            self.end_pos = event.pos()
            self.update()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton and self.is_selecting:
            self.is_selecting = False
            self.end_pos = event.pos()

            x = min(self.start_pos.x(), self.end_pos.x())
            y = min(self.start_pos.y(), self.end_pos.y())
            width = abs(self.start_pos.x() - self.end_pos.x())
            height = abs(self.start_pos.y() - self.end_pos.y())

            screen = QApplication.primaryScreen()
            pixmap = screen.grabWindow(0, x, y, width, height)

            image_path = os.path.join(self.parent.screenshot_dir, f"screenshot_{int(time.time())}.png")
            pixmap.save(image_path)

            self.image_captured.emit(image_path)
            self.close()

    def paintEvent(self, event):
        if self.is_selecting and self.start_pos and self.end_pos:
            painter = QPainter(self)
            painter.setPen(QPen(Qt.red, 2, Qt.DashLine))
            x = min(self.start_pos.x(), self.end_pos.x())
            y = min(self.start_pos.y(), self.end_pos.y())
            width = abs(self.start_pos.x() - self.end_pos.x())
            height = abs(self.start_pos.y() - self.end_pos.y())
            painter.drawRect(x, y, width, height)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.cancel_screenshot()

    def closeEvent(self, event):
        self.closed.emit()
        event.accept()


class TextInputDialog(QDialog):
    """文字输入对话框"""
    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        self.setWindowTitle("添加文字输入")
        self.setModal(True)
        self.setFixedSize(
            parent.scaled_value(400),
            parent.scaled_value(220)
        )
        
        layout = QVBoxLayout()
        layout.setSpacing(parent.scaled_value(10))
        layout.setContentsMargins(
            parent.scaled_value(15),
            parent.scaled_value(15),
            parent.scaled_value(15),
            parent.scaled_value(15)
        )
        
        label = QLabel("请输入要输出的文字:")
        layout.addWidget(label)
        
        self.text_edit = QTextEdit()
        self.text_edit.setPlaceholderText("在此输入文字...")
        self.text_edit.setStyleSheet(f"padding: {parent.scaled_value(5)}px; font-size: {parent.scaled_value(12)}px;")
        self.text_edit.setMinimumHeight(parent.scaled_value(100))
        layout.addWidget(self.text_edit)
        
        button_layout = QHBoxLayout()
        button_layout.setSpacing(parent.scaled_value(10))
        ok_button = QPushButton("确定")
        ok_button.setMinimumHeight(parent.scaled_value(35))
        ok_button.clicked.connect(self.validate_and_accept)
        cancel_button = QPushButton("取消")
        cancel_button.setMinimumHeight(parent.scaled_value(35))
        cancel_button.clicked.connect(self.reject)
        button_layout.addWidget(ok_button)
        button_layout.addWidget(cancel_button)
        layout.addLayout(button_layout)
        
        self.setLayout(layout)
        self.text_edit.setFocus()

    def validate_and_accept(self):
        """验证输入并确认"""
        text = self.text_edit.toPlainText().strip()
        if not text:
            QMessageBox.warning(self, "警告", "请写入需要输出的文字。")
            return
        self.accept()

    def get_text(self):
        return self.text_edit.toPlainText().strip()


class KeyboardInputDialog(QDialog):
    """键盘输入对话框(支持三种操作模式)"""
    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        self.setWindowTitle("添加键盘操作")
        self.setModal(True)
        self.setFixedSize(
            parent.scaled_value(550),
            parent.scaled_value(450)
        )
        
        self.key_sequence = ""
        self.modifiers = set()
        self.captured_image_path = None
        
        layout = QVBoxLayout()
        layout.setSpacing(parent.scaled_value(10))
        layout.setContentsMargins(
            parent.scaled_value(15),
            parent.scaled_value(15),
            parent.scaled_value(15),
            parent.scaled_value(15)
        )
        
        # 按键输入部分
        label = QLabel("请在下方输入框中按下要添加的按键(支持组合键):")
        layout.addWidget(label)
        
        self.key_edit = QLineEdit()
        self.key_edit.setReadOnly(True)
        self.key_edit.setPlaceholderText("点击此处后按下按键...")
        self.key_edit.setStyleSheet(f"padding: {parent.scaled_value(10)}px; font-size: {parent.scaled_value(14)}px;")
        self.key_edit.setMinimumHeight(parent.scaled_value(40))
        layout.addWidget(self.key_edit)
        
        # 操作模式选择组
        mode_group = QGroupBox("操作模式(只能选择一项)")
        mode_layout = QVBoxLayout()
        
        self.mode_group = QButtonGroup()
        
        # 模式1:按键一次
        self.radio_single = QRadioButton("按键一次(执行操作时按下添加的按键一次)")
        self.radio_single.setChecked(True)
        self.mode_group.addButton(self.radio_single, 0)
        mode_layout.addWidget(self.radio_single)
        
        # 模式2:长按指定时间
        self.radio_hold_time = QRadioButton("长按键盘(按住添加的按键不放,直到设置的时长到了则松开)")
        self.mode_group.addButton(self.radio_hold_time, 1)
        mode_layout.addWidget(self.radio_hold_time)
        
        # 长按时长设置
        hold_time_layout = QHBoxLayout()
        hold_time_layout.addWidget(QLabel("  按住时间(毫秒):"))
        self.hold_time_spin = QSpinBox()
        self.hold_time_spin.setRange(100, 60000)  # 100ms到60秒
        self.hold_time_spin.setValue(1000)  # 默认1秒
        self.hold_time_spin.setSuffix(" ms")
        self.hold_time_spin.setMinimumHeight(parent.scaled_value(30))
        self.hold_time_spin.setEnabled(False)
        hold_time_layout.addWidget(self.hold_time_spin)
        mode_layout.addLayout(hold_time_layout)
        
        # 模式3:按住直到图像出现
        self.radio_hold_until_image = QRadioButton("按住直到图像出现松开按键(按住添加的按键不放,直到指定图像出现后松开)")
        self.mode_group.addButton(self.radio_hold_until_image, 2)
        mode_layout.addWidget(self.radio_hold_until_image)
        
        # 图像选择部分
        image_layout = QHBoxLayout()
        image_layout.addWidget(QLabel("  等待图像:"))
        self.image_path_label = QLabel("未选择图像")
        self.image_path_label.setStyleSheet("padding: 5px; border: 1px solid #ccc; background-color: #f9f9f9;")
        self.image_path_label.setMinimumHeight(parent.scaled_value(30))
        image_layout.addWidget(self.image_path_label)
        
        self.select_image_btn = QPushButton("选择图像")
        self.select_image_btn.setMinimumHeight(parent.scaled_value(30))
        self.select_image_btn.clicked.connect(self.select_wait_image)
        self.select_image_btn.setEnabled(False)
        image_layout.addWidget(self.select_image_btn)
        mode_layout.addLayout(image_layout)
        
        mode_group.setLayout(mode_layout)
        layout.addWidget(mode_group)
        
        # 连接信号
        self.mode_group.buttonClicked.connect(self.on_mode_changed)
        self.radio_hold_time.toggled.connect(lambda checked: self.hold_time_spin.setEnabled(checked))
        self.radio_hold_until_image.toggled.connect(lambda checked: self.select_image_btn.setEnabled(checked))
        
        # 按钮
        button_layout = QHBoxLayout()
        button_layout.setSpacing(parent.scaled_value(10))
        ok_button = QPushButton("确定")
        ok_button.setMinimumHeight(parent.scaled_value(35))
        ok_button.clicked.connect(self.validate_and_accept)
        cancel_button = QPushButton("取消")
        cancel_button.setMinimumHeight(parent.scaled_value(35))
        cancel_button.clicked.connect(self.reject)
        button_layout.addWidget(ok_button)
        button_layout.addWidget(cancel_button)
        layout.addLayout(button_layout)
        
        self.setLayout(layout)
        self.key_edit.focusInEvent = self.on_focus_in
        self.key_edit.installEventFilter(self)
        
        # 保存当前对话框引用,以便主窗口可以访问
        self.parent.current_keyboard_dialog = self
    
    def on_focus_in(self, event):
        self.key_edit.clear()
        self.key_sequence = ""
        self.modifiers.clear()
    
    def on_mode_changed(self, button):
        """当操作模式改变时更新UI状态"""
        if button == self.radio_single:
            self.hold_time_spin.setEnabled(False)
            self.select_image_btn.setEnabled(False)
        elif button == self.radio_hold_time:
            self.hold_time_spin.setEnabled(True)
            self.select_image_btn.setEnabled(False)
        elif button == self.radio_hold_until_image:
            self.hold_time_spin.setEnabled(False)
            self.select_image_btn.setEnabled(True)
    
    def select_wait_image(self):
        """选择等待图像(从screenshots目录中选择)"""
        # 打开文件对话框,默认打开screenshots目录
        image_path, _ = QFileDialog.getOpenFileName(
            self, 
            "选择等待图像", 
            self.parent.screenshot_dir,  # 默认打开screenshots目录
            "图像文件 (*.png *.jpg *.jpeg *.bmp *.gif)"
        )
        
        if image_path:
            self.set_captured_image(image_path)
    
    def set_captured_image(self, image_path):
        """设置选择的图像路径"""
        self.captured_image_path = image_path
        self.image_path_label.setText(os.path.basename(image_path))
        self.image_path_label.setToolTip(image_path)
        print(f"键盘等待图像已设置: {image_path}")
    
    def get_action_type(self):
        """获取操作模式"""
        return self.mode_group.checkedId()
    
    def get_hold_time(self):
        """获取按住时间"""
        return self.hold_time_spin.value()
    
    def get_hold_image_path(self):
        """获取等待图像路径"""
        return self.captured_image_path
    
    def validate_and_accept(self):
        """验证输入并确认"""
        # 检查是否设置了按键
        if not self.key_sequence:
            QMessageBox.warning(self, "警告", "请先按下要添加的按键。")
            return
        
        # 检查模式3是否设置了图像
        if self.get_action_type() == 2 and not self.captured_image_path:
            QMessageBox.warning(self, "警告", "请为'按住直到图像出现'模式选择等待图像。")
            return
        
        self.accept()
    
    def eventFilter(self, obj, event):
        if obj == self.key_edit and event.type() == QEvent.KeyPress:
            self.handle_key_press(event)
            return True
        return super().eventFilter(obj, event)

    def handle_key_press(self, event):
        key = event.key()
        modifiers = event.modifiers()
        
        self.modifiers.clear()
        if modifiers & Qt.ControlModifier:
            self.modifiers.add("Ctrl")
        if modifiers & Qt.AltModifier:
            self.modifiers.add("Alt")
        if modifiers & Qt.ShiftModifier:
            self.modifiers.add("Shift")
        
        if key == Qt.Key_Space:
            key_name = "Space"
        elif key == Qt.Key_Return or key == Qt.Key_Enter:
            key_name = "Enter"
        elif key == Qt.Key_Escape:
            key_name = "Esc"
        elif key == Qt.Key_Tab:
            key_name = "Tab"
        elif key == Qt.Key_Backspace:
            key_name = "Backspace"
        elif key == Qt.Key_Delete:
            key_name = "Delete"
        elif key == Qt.Key_Up:
            key_name = "Up"
        elif key == Qt.Key_Down:
            key_name = "Down"
        elif key == Qt.Key_Left:
            key_name = "Left"
        elif key == Qt.Key_Right:
            key_name = "Right"
        else:
            key_name = QKeySequence(key).toString()
        
        parts = list(self.modifiers)
        parts.append(key_name)
        self.key_sequence = "+".join(parts)
        
        self.key_edit.setText(self.key_sequence)

    def get_key_sequence(self):
        return self.key_sequence


class DragSettingsDialog(QDialog):
    """鼠标拖动设置对话框"""
    def __init__(self, parent=None, start_point=None, end_point=None):
        super().__init__(parent)
        self.parent = parent
        self.start_point = start_point
        self.end_point = end_point
        self.setWindowTitle("设置鼠标拖动")
        self.setModal(True)
        self.setFixedSize(
            parent.scaled_value(400),
            parent.scaled_value(250)
        )
        
        layout = QVBoxLayout()
        layout.setSpacing(parent.scaled_value(10))
        layout.setContentsMargins(
            parent.scaled_value(15),
            parent.scaled_value(15),
            parent.scaled_value(15),
            parent.scaled_value(15)
        )
        
        # 坐标信息
        info_label = QLabel(f"起点: {start_point}\n终点: {end_point}")
        layout.addWidget(info_label)
        
        # 拖动类型
        type_layout = QHBoxLayout()
        type_layout.addWidget(QLabel("拖动类型:"))
        self.drag_type_combo = QComboBox()
        self.drag_type_combo.addItems(["左键拖动", "右键拖动"])
        self.drag_type_combo.setMinimumHeight(parent.scaled_value(30))
        type_layout.addWidget(self.drag_type_combo)
        layout.addLayout(type_layout)
        
        # 开始延迟
        start_delay_layout = QHBoxLayout()
        start_delay_layout.addWidget(QLabel("开始延迟(毫秒):"))
        self.start_delay_spin = QSpinBox()
        self.start_delay_spin.setRange(0, 10000)
        self.start_delay_spin.setValue(1000)  # 默认1秒
        self.start_delay_spin.setSuffix(" ms")
        self.start_delay_spin.setMinimumHeight(parent.scaled_value(30))
        start_delay_layout.addWidget(self.start_delay_spin)
        layout.addLayout(start_delay_layout)
        
        # 拖动持续时间
        duration_layout = QHBoxLayout()
        duration_layout.addWidget(QLabel("拖动持续时间(毫秒):"))
        self.duration_spin = QSpinBox()
        self.duration_spin.setRange(0, 10000)
        self.duration_spin.setValue(1000)  # 默认1秒
        self.duration_spin.setSuffix(" ms")
        self.duration_spin.setMinimumHeight(parent.scaled_value(30))
        duration_layout.addWidget(self.duration_spin)
        layout.addLayout(duration_layout)
        
        # 按钮
        button_layout = QHBoxLayout()
        ok_button = QPushButton("确定")
        ok_button.setMinimumHeight(parent.scaled_value(35))
        ok_button.clicked.connect(self.accept)
        cancel_button = QPushButton("取消")
        cancel_button.setMinimumHeight(parent.scaled_value(35))
        cancel_button.clicked.connect(self.reject)
        button_layout.addWidget(ok_button)
        button_layout.addWidget(cancel_button)
        layout.addLayout(button_layout)
        
        self.setLayout(layout)
    
    def get_drag_type(self):
        return 'left' if self.drag_type_combo.currentIndex() == 0 else 'right'
    
    def get_start_delay(self):
        return self.start_delay_spin.value()
    
    def get_duration(self):
        return self.duration_spin.value()


class ExecutionThread(QThread):
    finished = pyqtSignal()
    error = pyqtSignal(str)
    step_completed = pyqtSignal(int)

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.loop_count = 1
        self.current_step_index = 0
        self.is_stopped = False
        self.is_paused = False
        self.current_loop = 0

    def set_params(self, loop_count, start_index=0):
        self.loop_count = loop_count
        self.current_step_index = start_index
        self.current_loop = 0
        self.is_stopped = False
        self.is_paused = False

    def stop(self):
        self.is_stopped = True

    def pause(self):
        self.is_paused = True

    def resume(self):
        self.is_paused = False

    def run(self):
        try:
            self.is_stopped = False
            self.is_paused = False
            self.current_loop = 0
            self.current_step_index = 0  # 初始重置指针
            loop_count = self.loop_count
    
            # 外层定时总循环:直到停止/超时
            while not self.is_stopped:
                # 每次循环重新读取界面最新定时配置
                if self.parent.timed_execution_enabled:
                    start_str = self.parent.start_datetime_edit.text()
                    end_str = self.parent.end_datetime_edit.text()
                    start_dt = QDateTime.fromString(start_str, "yyyy-MM-dd HH:mm")
                    if not start_dt.isValid():
                        start_dt = QDateTime.fromString(start_str, "yyyy-MM-dd HH:mm:ss")
                    end_dt = QDateTime.fromString(end_str, "yyyy-MM-dd HH:mm")
                    if not end_dt.isValid():
                        end_dt = QDateTime.fromString(end_str, "yyyy-MM-dd HH:mm:ss")

                    current_dt = QDateTime.currentDateTime()
                    print(f"定时校验|当前:{current_dt.toString('yyyy-MM-dd HH:mm')} 目标启动:{start_str} 结束:{end_str}")
                   
                 # 超过结束时间直接退出
                    if current_dt > end_dt:
                        print("已超过定时结束时间,停止执行")
                        break

                    # 未到开始时间,每秒刷新时间等待,支持暂停/停止
                    while current_dt < start_dt and not self.is_stopped:
                        # 暂停状态卡住等待
                        while self.is_paused and not self.is_stopped:
                            time.sleep(0.1)
                        if self.is_stopped:
                            break
                        # 1秒刷新一次系统时间,适配修改电脑时间场景
                        time.sleep(1)
    
                        # 重新读取最新时间字符串
                        start_str = self.parent.start_datetime_edit.text()
                        end_str = self.parent.end_datetime_edit.text()
                        start_dt = QDateTime.fromString(start_str, "yyyy-MM-dd HH:mm")
                        if not start_dt.isValid():
                            start_dt = QDateTime.fromString(start_str, "yyyy-MM-dd HH:mm:ss")
                        end_dt = QDateTime.fromString(end_str, "yyyy-MM-dd HH:mm")
                        if not end_dt.isValid():
                            end_dt = QDateTime.fromString(end_str, "yyyy-MM-dd HH:mm:ss")
                        current_dt = QDateTime.currentDateTime()
                        print(f"等待中|当前:{current_dt.toString('yyyy-MM-dd HH:mm')} 等待启动:{start_str}")
                    # 等待结束后再次判断是否停止/超时
                    if self.is_stopped or current_dt > end_dt:
                        break
    
                # 【每轮执行前重置步骤指针】
                self.current_step_index = 0
                self.current_loop += 1
                run_one_loop = True
                # 执行一轮完整步骤
                while run_one_loop and not self.is_stopped:
                    # 暂停阻塞
                    while self.is_paused and not self.is_stopped:
                        time.sleep(0.1)
                    if self.is_stopped:
                        break

                    # 单步前重新校验定时
                    if self.parent.timed_execution_enabled:
                        end_str = self.parent.end_datetime_edit.text()
                        end_dt = QDateTime.fromString(end_str, "yyyy-MM-dd HH:mm")
                        if not end_dt.isValid():
                            end_dt = QDateTime.fromString(end_str, "yyyy-MM-dd HH:mm:ss")
                        if QDateTime.currentDateTime() > end_dt:
                            print("执行中单步检测已超时,终止流程")
                            self.is_stopped = True
                            break

                    if self.current_step_index >= len(self.parent.steps):
                        run_one_loop = False
                        break

                    step = self.parent.steps[self.current_step_index]
                    # 键盘步骤
                    if step.step_type == 'keyboard':
                        self.execute_keyboard_step(step)
                        time.sleep(step.wait_time / 1000.0)
                        self.current_step_index += 1
                        self.step_completed.emit(self.current_step_index)
                        continue
                    # 文字输入步骤
                    if step.step_type == 'text':
                        self.execute_text_step(step)
                        time.sleep(step.wait_time / 1000.0)
                        self.current_step_index += 1
                        self.step_completed.emit(self.current_step_index)
                        continue
                    # 拖动步骤
                    if step.step_type == 'drag':
                        self.execute_drag_step(step)
                        time.sleep(step.wait_time / 1000.0)
                        self.current_step_index += 1
                        self.step_completed.emit(self.current_step_index)
                        continue

                    # 图像/坐标点击逻辑(原代码完全保留无修改)
                    target_pos = None
                    if step.step_type == 'image':
                        target_pos = self.find_image(step.image_path, step.similarity)
                        if target_pos and self.parent.target_hwnd:
                            left, top, _, _ = win32gui.GetWindowRect(self.parent.target_hwnd)
                            target_pos = (target_pos[0] + left, target_pos[1] + top)
                    else:
                        target_pos = (step.x, step.y)
                        if self.parent.target_hwnd:
                            left, top, _, _ = win32gui.GetWindowRect(self.parent.target_hwnd)
                            target_pos = (target_pos[0] + left, target_pos[1] + top)

                    total_wait = step.wait_time
                    if step.accept_random_delay and step.random_delay_enabled:
                        total_wait += random.randint(step.random_delay_min, step.random_delay_max)

                    # 图像跳转逻辑不变
                    if step.step_type == 'image':
                        if target_pos is not None:
                            if step.jump_on_found is not None:
                                self.current_step_index = step.jump_on_found
                                self.step_completed.emit(self.current_step_index)
                                time.sleep(total_wait / 1000.0)
                                continue
                        else:
                            if step.jump_on_not_found is not None:
                                self.current_step_index = step.jump_on_not_found
                                self.step_completed.emit(self.current_step_index)
                                time.sleep(total_wait / 1000.0)
                                continue

                    if target_pos is not None:
                        self.click_target(target_pos, step)
                    else:
                        print(f"步骤 {self.current_step_index + 1}: 未找到目标,跳过")

                    time.sleep(total_wait / 1000.0)
                    self.current_step_index += 1
                    self.step_completed.emit(self.current_step_index)

                # 判断是否达到循环次数上限
                if loop_count != 999999 and self.current_loop >= loop_count:
                    print("达到循环次数上限,执行结束")
                    break

                time.sleep(0.5)
            self.finished.emit()
        except Exception as e:
            self.error.emit(f"线程异常:{str(e)}")

    def execute_text_step(self, step):
        """执行文字输入步骤"""
        try:
            if self.parent.target_hwnd:
                # 激活目标窗口
                win32gui.SetForegroundWindow(self.parent.target_hwnd)
                time.sleep(0.1)
            
            # 输入文字
            self.parent.keyboard.type(step.text_content)
            print(f"输入文字: {step.text_content[:20]}...")
        except Exception as e:
            print(f"文字输入失败: {e}")

    def execute_drag_step(self, step):
        """执行鼠标拖动步骤(支持缓冲时间)"""
        try:
            start_x, start_y = step.drag_start_x, step.drag_start_y
            end_x, end_y = step.drag_end_x, step.drag_end_y

            if self.parent.target_hwnd:
                left, top, _, _ = win32gui.GetWindowRect(self.parent.target_hwnd)
                start_x += left
                start_y += top
                end_x += left
                end_y += top
                
                # 后台拖动(支持延迟)
                self.drag_backend_with_delay(
                    self.parent.target_hwnd, 
                    start_x, start_y, 
                    end_x, end_y, 
                    step.click_type,
                    step.drag_start_delay,
                    step.drag_duration
                )
            else:
                # 前台拖动(支持延迟)
                self.drag_foreground_with_delay(
                    start_x, start_y, 
                    end_x, end_y, 
                    step.click_type,
                    step.drag_start_delay,
                    step.drag_duration
                )
                
            print(f"鼠标拖动: ({start_x},{start_y}) -> ({end_x},{end_y}), "
                  f"按钮: {step.click_type}, "
                  f"开始延迟: {step.drag_start_delay}ms, "
                  f"持续时间: {step.drag_duration}ms")
        except Exception as e:
            print(f"鼠标拖动失败: {e}")

    def drag_foreground_with_delay(self, start_x, start_y, end_x, end_y, button_type, start_delay, duration):
        """前台鼠标拖动(支持延迟)"""
        # 移动鼠标到起点
        self.parent.mouse.position = (start_x, start_y)
        time.sleep(0.05)
        
        # 按下鼠标键
        button = Button.left if button_type == 'left' else Button.right
        self.parent.mouse.press(button)
        
        # 开始延迟(在起点等待)
        if start_delay > 0:
            time.sleep(start_delay / 1000.0)
        
        # 移动到终点(支持平滑移动)
        if duration > 0:
            # 平滑移动
            steps = max(10, int(duration / 10))  # 至少10步
            step_delay = duration / steps / 1000.0
            
            for i in range(1, steps + 1):
                curr_x = start_x + (end_x - start_x) * i / steps
                curr_y = start_y + (end_y - start_y) * i / steps
                self.parent.mouse.position = (curr_x, curr_y)
                time.sleep(step_delay)
        else:
            # 立即移动
            self.parent.mouse.position = (end_x, end_y)
        
        time.sleep(0.05)
        # 释放鼠标键
        self.parent.mouse.release(button)

    def drag_backend_with_delay(self, hwnd, start_x, start_y, end_x, end_y, button_type, start_delay, duration):
        """后台鼠标拖动(支持延迟)"""
        # 转换到窗口相对坐标
        left, top, _, _ = win32gui.GetWindowRect(hwnd)
        rel_start_x = start_x - left
        rel_start_y = start_y - top
        rel_end_x = end_x - left
        rel_end_y = end_y - top
        
        # 确定消息类型
        if button_type == 'left':
            down_msg, up_msg = win32con.WM_LBUTTONDOWN, win32con.WM_LBUTTONUP
            mk_flag = win32con.MK_LBUTTON
        else:
            down_msg, up_msg = win32con.WM_RBUTTONDOWN, win32con.WM_RBUTTONUP
            mk_flag = win32con.MK_RBUTTON
        
        # 按下起点
        lparam_down = win32api.MAKELONG(rel_start_x, rel_start_y)
        win32gui.SendMessage(hwnd, down_msg, mk_flag, lparam_down)
        
        # 开始延迟(在起点等待)
        if start_delay > 0:
            time.sleep(start_delay / 1000.0)
        
        # 移动到终点(支持平滑移动)
        if duration > 0:
            steps = max(10, int(duration / 10))  # 至少10步
            step_delay = duration / steps / 1000.0
            
            for i in range(1, steps + 1):
                curr_x = rel_start_x + (rel_end_x - rel_start_x) * i / steps
                curr_y = rel_start_y + (rel_end_y - rel_start_y) * i / steps
                lparam_move = win32api.MAKELONG(int(curr_x), int(curr_y))
                win32gui.SendMessage(hwnd, win32con.WM_MOUSEMOVE, mk_flag, lparam_move)
                time.sleep(step_delay)
        else:
            # 立即移动
            lparam_move = win32api.MAKELONG(rel_end_x, rel_end_y)
            win32gui.SendMessage(hwnd, win32con.WM_MOUSEMOVE, mk_flag, lparam_move)
        
        time.sleep(0.01)
        
        # 释放终点
        lparam_up = win32api.MAKELONG(rel_end_x, rel_end_y)
        win32gui.SendMessage(hwnd, up_msg, 0, lparam_up)

    def execute_keyboard_step(self, step):
        """执行键盘步骤(支持三种操作模式)"""
        try:
            if self.parent.target_hwnd:
                # 激活目标窗口
                win32gui.SetForegroundWindow(self.parent.target_hwnd)
                time.sleep(0.1)
            
            keys = step.key_sequence.split('+')
            
            # 按下修饰键
            for key in keys[:-1]:
                if key == 'Ctrl':
                    self.parent.keyboard.press(Key.ctrl)
                elif key == 'Alt':
                    self.parent.keyboard.press(Key.alt)
                elif key == 'Shift':
                    self.parent.keyboard.press(Key.shift)
            
            # 按下主键
            main_key = keys[-1]
            vk_code = None
            
            # 映射特殊键
            special_keys = {
                'Space': Key.space,
                'Enter': Key.enter,
                'Esc': Key.esc,
                'Tab': Key.tab,
                'Backspace': Key.backspace,
                'Delete': Key.delete,
                'Up': Key.up,
                'Down': Key.down,
                'Left': Key.left,
                'Right': Key.right
            }
            
            if main_key in special_keys:
                self.parent.keyboard.press(special_keys[main_key])
                vk_code = special_keys[main_key]
            elif len(main_key) == 1:
                self.parent.keyboard.press(main_key.lower())
                vk_code = main_key.lower()
            
            # 根据操作模式执行不同逻辑
            if step.key_action_type == 0:  # 按键一次
                # 立即释放
                if vk_code:
                    if isinstance(vk_code, Key):
                        self.parent.keyboard.release(vk_code)
                    else:
                        self.parent.keyboard.release(vk_code)
                print(f"执行键盘操作(按键一次): {step.key_sequence}")
                
            elif step.key_action_type == 1:  # 长按指定时间
                if step.key_hold_time > 0:
                    # 保持按键按下状态,直到时间到达
                    start_time = time.time()
                    elapsed = 0
                    while elapsed < (step.key_hold_time / 1000.0) and not self.is_stopped:
                        time.sleep(0.001)  # 短暂休眠,避免CPU占用过高
                        elapsed = time.time() - start_time
                
                # 释放按键
                if vk_code:
                    if isinstance(vk_code, Key):
                        self.parent.keyboard.release(vk_code)
                    else:
                        self.parent.keyboard.release(vk_code)
                print(f"执行键盘操作(长按{step.key_hold_time}ms): {step.key_sequence}")
                
            elif step.key_action_type == 2:  # 按住直到图像出现
                if step.key_hold_image_path and os.path.exists(step.key_hold_image_path):
                    print(f"开始等待图像出现: {step.key_hold_image_path}")
                    
                    # 无限等待直到图像出现或线程被停止
                    image_found = False
                    while not self.is_stopped and not image_found:
                        # 检查图像是否出现
                        if self.find_image(step.key_hold_image_path, 0.8):
                            print(f"检测到目标图像,释放按键: {step.key_sequence}")
                            image_found = True
                            break
                        
                        time.sleep(0.1)  # 短暂休眠,避免CPU占用过高
                    
                    # 释放按键
                    if vk_code:
                        if isinstance(vk_code, Key):
                            self.parent.keyboard.release(vk_code)
                        else:
                            self.parent.keyboard.release(vk_code)
                    
                    if self.is_stopped:
                        print("等待图像过程中出现停止信号,释放按键并退出")
                    elif image_found:
                        print(f"执行键盘操作(按住直到图像出现): {step.key_sequence}")
                        # 只有找到图像后才继续到下一步
                        # 注意:这里不增加current_step_index,由主循环控制
                else:
                    print(f"警告: 未设置有效的等待图像,执行普通按键: {step.key_sequence}")
                    # 如果没有有效图像,当作普通按键处理
                    if vk_code:
                        if isinstance(vk_code, Key):
                            self.parent.keyboard.release(vk_code)
                        else:
                            self.parent.keyboard.release(vk_code)
            
            # 释放修饰键
            for key in reversed(keys[:-1]):
                if key == 'Ctrl':
                    self.parent.keyboard.release(Key.ctrl)
                elif key == 'Alt':
                    self.parent.keyboard.release(Key.alt)
                elif key == 'Shift':
                    self.parent.keyboard.release(Key.shift)
                    
        except Exception as e:
            print(f"键盘操作执行失败: {e}")

    def find_image(self, image_path, similarity=0.8):
        print(f"尝试查找图像: {image_path}")
        print(f"文件是否存在: {os.path.exists(image_path)}")
        
        if not os.path.exists(image_path):
            print(f"警告: 图像文件不存在: {image_path}")
            return None

        # 支持中文路径的读取方式
        try:
            img_array = np.fromfile(image_path, dtype=np.uint8)
            template = cv2.imdecode(img_array, cv2.IMREAD_GRAYSCALE)
        except Exception as e:
            print(f"读取图像文件失败: {e}")
            return None
            
        if template is None:
            print(f"警告: 无法读取图像文件: {image_path}")
            return None

        # 获取截图区域
        if self.parent.target_hwnd:
            left, top, right, bottom = win32gui.GetWindowRect(self.parent.target_hwnd)
            if right - left <= 0 or bottom - top <= 0:
                return None
            
            bbox = (left, top, right, bottom)
            screen_image = ImageGrab.grab(bbox)
        else:
            screen = QApplication.primaryScreen()
            screen_image = screen.grabWindow(0).toImage()

        if isinstance(screen_image, QImage):
            width = screen_image.width()
            height = screen_image.height()
            ptr = screen_image.bits()
            ptr.setsize(height * width * 4)
            arr = np.array(ptr).reshape(height, width, 4)
            screenshot = cv2.cvtColor(arr, cv2.COLOR_BGRA2GRAY)
        else:
            screenshot = cv2.cvtColor(np.array(screen_image), cv2.COLOR_RGB2GRAY)

        if screenshot.shape[0] < template.shape[0] or screenshot.shape[1] < template.shape[1]:
            return None

        result = cv2.matchTemplate(screenshot, template, cv2.TM_CCOEFF_NORMED)
        _, max_val, _, max_loc = cv2.minMaxLoc(result)

        print(f"匹配相似度: {max_val}")

        if max_val >= similarity:
            h, w = template.shape[:2]
            center_x = max_loc[0] + w // 2
            center_y = max_loc[1] + h // 2
            
            return (center_x, center_y)

        return None

    def click_target(self, target_pos, step):
        x, y = target_pos

        if step.accept_offset and self.parent.offset_enabled:
            x += random.randint(
                -self.parent.offset_x,
                self.parent.offset_x
            )
            y += random.randint(
                -self.parent.offset_y,
                self.parent.offset_y
            )

        if self.parent.target_hwnd:
            left, top, _, _ = win32gui.GetWindowRect(self.parent.target_hwnd)
            rel_x, rel_y = x - left, y - top

            if step.click_type == 'left':
                down_msg, up_msg = win32con.WM_LBUTTONDOWN, win32con.WM_LBUTTONUP
            elif step.click_type == 'right':
                down_msg, up_msg = win32con.WM_RBUTTONDOWN, win32con.WM_RBUTTONUP
            elif step.click_type == 'double':
                self.click_target_backend(self.parent.target_hwnd, rel_x, rel_y,
                                         win32con.WM_LBUTTONDOWN, win32con.WM_LBUTTONUP)
                time.sleep(0.05)
                self.click_target_backend(self.parent.target_hwnd, rel_x, rel_y,
                                         win32con.WM_LBUTTONDOWN, win32con.WM_LBUTTONUP)
                return
            else:
                return

            self.click_target_backend(self.parent.target_hwnd, rel_x, rel_y, down_msg, up_msg)
        else:
            self.parent.mouse.position = (x, y)
            time.sleep(0.05)
            if step.click_type == 'left':
                self.parent.mouse.click(Button.left)
            elif step.click_type == 'right':
                self.parent.mouse.click(Button.right)
            elif step.click_type == 'double':
                self.parent.mouse.click(Button.left)
                time.sleep(0.05)
                self.parent.mouse.click(Button.left)

    def click_target_backend(self, hwnd, x, y, down_msg, up_msg):
        lparam = win32api.MAKELONG(x, y)
        win32gui.SendMessage(hwnd, down_msg, win32con.MK_LBUTTON, lparam)
        time.sleep(0.01)
        win32gui.SendMessage(hwnd, up_msg, 0, lparam)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = AutoClickerApp()
    window.show()
    sys.exit(app.exec_())

免费评分

参与人数 4吾爱币 +4 热心值 +4 收起 理由
DBGx + 1 + 1 热心回复!请教使用前如何编译?
Coco5333 + 1 + 1 感谢分享!
泡泡汽水 + 1 + 1 感谢分享!!
Dongfanglong + 1 + 1 热心回复!

查看全部评分

推荐
落雪长风 发表于 2026-7-17 10:58
推荐
东莞洪世贤 发表于 2026-7-17 09:46
推荐
mikado 发表于 2026-7-17 10:13
按键精灵方便版?
推荐
Yadiel 发表于 2026-7-20 15:20
有用,好软件
沙发
plwt 发表于 2026-7-17 08:51
必须要顶,感谢楼主的付出
3#
zcyxh12345 发表于 2026-7-17 08:52
感谢楼主
4#
flyhu 发表于 2026-7-17 08:56
谢谢,试试。。。。。
5#
liyu0828 发表于 2026-7-17 08:58
感谢分享
6#
25809127 发表于 2026-7-17 08:59
还挺好用,谢谢。
7#
FeACuM 发表于 2026-7-17 09:00
试用一下,感谢分享
8#
yanyao253 发表于 2026-7-17 09:05
谢谢分享,很适用
9#
fenghen0606 发表于 2026-7-17 09:06
正好需要用,感谢老板
10#
木木与虾米 发表于 2026-7-17 09:06
感谢分享,收藏备用
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-8-13 16:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表