[Windows] 自动点击工具(图片识别与坐标点击)V1.0,来自吾爱论坛的自开发工具

本文最后更新于 2026年5月23日 by 阿喵

发布原因


因为有时候玩的游戏刷材料时需要鼠标点来点去太累了,然后再论坛里找了一圈相关鼠标点击类的工具,找到很多款,但没找到符合我预期效果的工具。作为小白,只能借助Ai一步一步摸索来达到我想要的效果,所以就有了这个工具,下面附上源码便于其他大佬进行深化。

简介界面

该工具集成了图像识别点击与坐标点击两种模式,支持指定窗口后台执行、可视化流程编排,可用于游戏挂机、办公自动化等场景,提升重复操作效率。

[Windows] 自动点击工具(图片识别与坐标点击)V1.0,来自吾爱论坛的自开发工具

核心特点


双模式点击:图像识别点击(支持模板匹配,可在屏幕或指定窗口中自动寻找预设的图片目标并点击,自带相似度阈值调节)和坐标点击(支持添加绝对屏幕坐标或相对窗口坐标),两种模式可在同一流程中自由组合;
后台执行:指定窗口绑定(通过“选取窗口”功能锁定目标程序(HWND))和后台消息注入(使窗口处于后台或被遮挡,也能通过 Windows 消息机制模拟点击,不干扰前台工作,也不影响鼠标键盘的正常使用);
可视化流程编排:图形化步骤列表中支持增删和上下移动,每一步均可独立配置点击类型、等待时间、相似度等参数,并支持步骤跳转,实现条件分支逻辑;
全局热键控制:支持自定义全局快捷键(默认F12),可一键启动或停止任务无需切换窗口即可控制运行状态;
灵活循环策略:支持无限循环与指定次数循环;
配置保存与复用:支持一键保存、加载配置文件。

使用说明


控制流程:添加好步骤或加载配置后可以开始执行、暂停和停止执行;
目标窗口设置:用于后台执行点击,不影响操作(注:点击原理依赖 Windows 消息队列​ ,是通过 SendMessage发送消息给系统,系统会转发给窗口实现点击的,而直接从驱动层读取硬件输入(鼠标/键盘中断)的游戏则无法实现点击效果);
文件操作:用于保存步骤列表的流程,便于下一次加载使用;
循环设置:可以设置步骤列表的执行次数,默认无限循环;
偏移设置:用于模拟坐标点(图像的中心位置)进行鼠标点击的上下左右偏移,通过随机微调点击位置,避免一些游戏对鼠标操作的检测;
随机延迟:在步骤列表中设置的等待时间的基础上再增加随机延时,避免点击时间固定,降低被检测识别风险,默认随机延迟时间是在100毫秒至1000毫秒之间;
热键设置:用于一键开始执行或者暂停流程,且热键可以更换,避免和某些案件冲突;
操作步骤:添加图像(通过截取图像进行识别点击),添加坐标(在屏幕选点截取坐标),上移和下移(用于调整步骤的执行顺序),删除(用于删除不要的步骤);
步骤列表:步骤(流程执行的顺序),类型(图像识别点击和坐标点击),点击类型(左键单击、右键单击、双击、跳转,其中跳转是用于类型是图像识别,就是该步骤识别到图像就跳转到对应步骤去),等待(执行等待时间,1秒等于1000毫秒,默认1000毫秒),相似度(图像识别的相似度,默认0.8),接受偏移和接受随机延迟(可自定义步骤是否进行鼠标点击的偏移和点击延迟,默认勾选,但需要自行勾选上“启动偏移”和“启动随机延迟”),跳转设置(只能在“点击类型”为跳转的情况下才能设置为跳转步骤)。

源代码

import sys
import cv2
import numpy as np
import json
import os
import time
import random
import ctypes
import win32gui
import win32con
import win32api
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
 
# 设置 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,
                 jump_to=None, random_delay_enabled=False, random_delay_min=100, random_delay_max=1000,
                 accept_offset=True, accept_random_delay=True):
        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.jump_to = jump_to
        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
 
 
class AutoClickerApp(QMainWindow):
    coordinate_captured = pyqtSignal(int, int)
    stop_coordinate_mode = pyqtSignal()
    stop_select_window_mode_signal = pyqtSignal()
    screenshot_closed = pyqtSignal()
 
    def __init__(self):
        super().__init__()
        self.setWindowTitle("自动点击工具(图片识别与坐标点击)V1.0")
        self.setFixedSize(1000, 800)
 
        self.steps = []
        self.current_step_index = 0
        self.is_running = False
        self.is_paused = False
        self.is_adding_coordinate = False
        self.is_selecting_window = False
        self.is_adding_image = False
        self.screenshot_dir = "screenshots"
        self.mouse_listener = None
        self.keyboard_listener = None
        self.global_hotkey_listener = None
        self.target_hwnd = None
        self.base_dir = os.path.dirname(os.path.abspath(__file__))
 
        os.makedirs(self.screenshot_dir, exist_ok=True)
 
        self.mouse = MouseController()
        self.keyboard = KeyboardController()
        self.hotkey = "F12"
 
        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)
 
    def init_ui(self):
        font = QFont("Microsoft YaHei", 10)
        self.setFont(font)
 
        main_widget = QWidget()
        main_layout = QVBoxLayout()
        main_layout.setSpacing(10)
        main_layout.setContentsMargins(10, 10, 10, 10)
 
        main_splitter = QSplitter(Qt.Horizontal)
 
        left_widget = QWidget()
        left_layout = QVBoxLayout()
        left_layout.setSpacing(10)
        left_widget.setMaximumWidth(290)
 
        control_group = QGroupBox("流程控制")
        control_layout = QVBoxLayout()
 
        self.start_btn = QPushButton("▶ 开始执行")
        self.start_btn.clicked.connect(self.start_execution)
        self.stop_btn = QPushButton("⏹ 停止执行")
        self.stop_btn.clicked.connect(self.stop_execution)
        self.stop_btn.setEnabled(False)
        self.pause_btn = QPushButton("⏸ 暂停执行")
        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)
 
        window_group = QGroupBox("目标窗口设置 (后台执行关键)")
        window_layout = QVBoxLayout()
        self.window_title_edit = QLineEdit()
        self.window_title_edit.setPlaceholderText("点击下方按钮选择目标窗口")
        self.window_title_edit.setReadOnly(True)
 
        self.select_window_btn = QPushButton("🎯 选取窗口")
        self.select_window_btn.clicked.connect(self.start_select_window_mode)
 
        self.clear_window_btn = QPushButton("❌ 清除窗口")
        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)
 
        file_group = QGroupBox("文件操作")
        file_layout = QHBoxLayout()
        self.save_btn = QPushButton("💾 保存配置")
        self.save_btn.clicked.connect(self.save_config)
        self.load_btn = QPushButton("📂 加载配置")
        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)
 
        loop_group = QGroupBox("循环设置")
        loop_layout = QVBoxLayout()
        self.loop_type_group = QButtonGroup()
        self.radio_infinite = QRadioButton("无限循环")
        self.radio_count = QRadioButton("次数循环")
        self.radio_infinite.setChecked(True)
        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(1000)
        count_layout.addWidget(self.loop_spin)
        loop_layout.addLayout(count_layout)
        loop_group.setLayout(loop_layout)
        left_layout.addWidget(loop_group)
 
        offset_group = QGroupBox("偏移设置(X轴和Y轴点击偏移量)")
        offset_layout = QVBoxLayout()
        self.offset_checkbox = QCheckBox("启用偏移")
        self.offset_checkbox.setChecked(False)
        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(5)
        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(5)
        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)
        left_layout.addWidget(offset_group)
 
        random_delay_group = QGroupBox("启动随机延迟(1秒等于1000毫秒)")
        random_delay_layout = QVBoxLayout()
        self.random_delay_checkbox = QCheckBox("启动随机延迟")
        self.random_delay_checkbox.setChecked(False)
        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(100)
        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(1000)
        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)
        left_layout.addWidget(random_delay_group)
 
        hotkey_group = QGroupBox("热键设置")
        hotkey_layout = QVBoxLayout()
        self.hotkey_preset = QComboBox()
        self.hotkey_preset.addItems([
            "F12", "F11", "F10", "F9", "F8",
            "Ctrl+A", "Ctrl+S", "Ctrl+Shift+F",
            "Alt+F4", "Shift+F12", "Ctrl+F5"
        ])
        self.hotkey_preset.setCurrentText("F12")
        self.hotkey_preset.currentTextChanged.connect(self.on_hotkey_changed)
        hotkey_layout.addWidget(QLabel("启动/停止热键:"))
        hotkey_layout.addWidget(self.hotkey_preset)
        hotkey_group.setLayout(hotkey_layout)
        left_layout.addWidget(hotkey_group)
 
        left_layout.addStretch()
        left_widget.setLayout(left_layout)
 
        right_widget = QWidget()
        right_layout = QVBoxLayout()
        right_layout.setSpacing(10)
 
        top_widget = QWidget()
        top_layout = QHBoxLayout()
        top_layout.setSpacing(10)
 
        step_ops_group = QGroupBox("步骤操作")
        step_ops_layout = QGridLayout()
        self.add_image_btn = QPushButton("📷 添加图像")
        self.add_image_btn.clicked.connect(self.add_image_step)
        self.add_coordinate_btn = QPushButton("🎯 添加坐标")
        self.add_coordinate_btn.clicked.connect(self.start_add_coordinate_mode)
        self.delete_btn = QPushButton("🗑️ 删除")
        self.delete_btn.clicked.connect(self.delete_step)
        self.move_up_btn = QPushButton("⬆️ 上移")
        self.move_up_btn.clicked.connect(self.move_step_up)
        self.move_down_btn = QPushButton("⬇️ 下移")
        self.move_down_btn.clicked.connect(self.move_step_down)
        step_ops_layout.addWidget(self.add_image_btn, 0, 0)
        step_ops_layout.addWidget(self.add_coordinate_btn, 0, 1)
        step_ops_layout.addWidget(self.move_up_btn, 1, 0)
        step_ops_layout.addWidget(self.move_down_btn, 1, 1)
        step_ops_layout.addWidget(self.delete_btn, 2, 0, 1, 2)
        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(150)
        self.preview_label.setMaximumHeight(150)
        self.preview_label.setStyleSheet(
            "border: 1px solid #ccc; border-radius: 3px; "
            "background-color: #f9f9f9; font-size: 11px; padding: 5px;"
        )
        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(8)
        self.steps_table.setHorizontalHeaderLabels([
            "步骤", "类型", "点击类型", "等待(毫秒)",
            "相似度", "接受偏移", "接受随机延迟", "跳转设置"
        ])
 
        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("""
            QTableWidget {
                gridline-color: #ddd;
                font-size: 11px;
                border: 1px solid #ccc;
                border-radius: 3px;
            }
            QTableWidget::item {
                padding: 6px 4px;
                min-height: 20px;
                text-align: center;
            }
            QTableWidget::item:selected {
                background-color: #e3f2fd;
            }
            QHeaderView::section {
                background-color: #f0f0f0;
                padding: 6px;
                border: 1px solid #ddd;
                font-weight: bold;
                text-align: center;
            }
        """)
        self.steps_table.verticalHeader().setDefaultSectionSize(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([290, 710])
 
        main_layout.addWidget(main_splitter)
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)
 
    def clear_target_window(self):
        self.target_hwnd = None
        self.window_title_edit.clear()
        QMessageBox.information(self, "提示", "已清除目标窗口")
 
    def set_controls_enabled(self, enabled):
        controls = [
            self.radio_infinite, self.radio_count, self.loop_spin,
            self.offset_checkbox, self.offset_x_spin, self.offset_y_spin,
            self.random_delay_checkbox, self.random_delay_min_spin, self.random_delay_max_spin,
            self.hotkey_preset, self.steps_table, self.add_image_btn,
            self.add_coordinate_btn, self.delete_btn, self.move_up_btn,
            self.move_down_btn, self.save_btn, self.load_btn, self.select_window_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)
            self.global_hotkey_listener = KeyboardListener(
                on_press=lambda key: self.on_global_key_press(key, keys)
            )
            self.global_hotkey_listener.start()
        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)
            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)
        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(
            "border: 1px solid #ccc; background-color: #fff3cd; color: #856404;"
        )
 
        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(
            "border: 1px solid #ccc; background-color: #f9f9f9;"
        )
 
    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(
            "border: 1px solid #ccc; background-color: #fff3cd; color: #856404;"
        )
 
        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(
            "border: 1px solid #ccc; background-color: #f9f9f9;"
        )
 
    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 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_checkbox.isChecked(),
            random_delay_min=self.random_delay_min_spin.value(),
            random_delay_max=self.random_delay_max_spin.value()
        )
        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 on_hotkey_changed(self, text):
        self.hotkey = text.strip()
        self.setup_global_hotkey()
 
    def toggle_execution(self):
        if self.is_running:
            self.stop_execution()
        else:
            self.start_execution()
 
    def get_loop_count(self):
        return 999999 if self.radio_infinite.isChecked() else self.loop_spin.value()
 
    def start_execution(self):
        if not self.steps:
            QMessageBox.warning(self, "错误", "没有可执行的步骤")
            return
 
        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("⏸ 暂停执行")
        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("⏸ 暂停执行")
 
    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("▶ 继续执行")
        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("⏸ 暂停执行")
 
    def update_all_settings(self):
        for step in self.steps:
            step.random_delay_enabled = self.random_delay_checkbox.isChecked()
            step.random_delay_min = self.random_delay_min_spin.value()
            step.random_delay_max = self.random_delay_max_spin.value()
 
    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("⏸ 暂停执行")
 
    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("⏸ 暂停执行")
        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):
        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_checkbox.isChecked(),
            random_delay_min=self.random_delay_min_spin.value(),
            random_delay_max=self.random_delay_max_spin.value()
        )
        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)
 
            type_item = QTableWidgetItem("图像识别" if step.step_type == 'image' else "坐标点击")
            type_item.setTextAlignment(Qt.AlignCenter)
            self.steps_table.setItem(i, 1, type_item)
 
            click_combo = QComboBox()
            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)
            elif step.click_type == 'jump':
                click_combo.setCurrentIndex(3)
            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.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)
 
            jump_combo = QComboBox()
            jump_combo.addItem("无跳转", -1)
            for j in range(len(self.steps)):
                jump_combo.addItem(f"步骤 {j + 1}", j)
 
            if step.jump_to is not None and step.jump_to < len(self.steps):
                jump_combo.setCurrentIndex(step.jump_to + 1)
 
            jump_combo.currentIndexChanged.connect(
                lambda index, row=i: self.update_step_jump_to(row, index)
            )
 
            if step.step_type == 'image' and step.click_type == 'jump':
                jump_combo.setEnabled(True)
            else:
                jump_combo.setEnabled(False)
                jump_combo.setCurrentIndex(0)
                if step.step_type == 'coordinate':
                    step.click_type = 'left'
                    step.jump_to = None
 
            self.steps_table.setCellWidget(i, 7, jump_combo)
 
    def update_step_jump_to(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_to = 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 = 'jump'
 
            jump_combo = self.steps_table.cellWidget(row, 7)
            if jump_combo:
                if text == "跳转" and self.steps[row].step_type == 'image':
                    jump_combo.setEnabled(True)
                else:
                    jump_combo.setEnabled(False)
                    self.steps[row].jump_to = None
                    jump_combo.setCurrentIndex(0)
 
    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(300, 150, Qt.KeepAspectRatio))
            else:
                self.preview_label.setText(f"坐标: ({step.x}, {step.y})")
 
    def save_config(self):
        file_path, _ = QFileDialog.getSaveFileName(self, "保存配置", "", "JSON文件 (*.json)")
        if not file_path:
            return
 
        config = {
            "steps": [],
            "hotkey": self.hotkey,
            "loop_type": self.loop_type_group.checkedId(),
            "loop_count": self.loop_spin.value(),
            "offset_enabled": self.offset_checkbox.isChecked(),
            "offset_x": self.offset_x_spin.value(),
            "offset_y": self.offset_y_spin.value(),
            "random_delay_enabled": self.random_delay_checkbox.isChecked(),
            "random_delay_min": self.random_delay_min_spin.value(),
            "random_delay_max": self.random_delay_max_spin.value()
        }
 
        config_dir = os.path.dirname(file_path)
 
        for step in 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,
                "jump_to": step.jump_to,
                "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
            }
 
            if step.step_type == "image" and step.image_path:
                src_path = step.image_path
                if os.path.isabs(src_path):
                    dst_path = os.path.join(config_dir, "screenshots", os.path.basename(src_path))
                    os.makedirs(os.path.dirname(dst_path), exist_ok=True)
                    import shutil
                    shutil.copy2(src_path, dst_path)
                    step_data["image_path"] = os.path.relpath(dst_path, config_dir)
 
            config["steps"].append(step_data)
 
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(config, f, indent=2, ensure_ascii=False)
 
        QMessageBox.information(self, "成功", "配置保存成功!")
 
    def load_config(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "加载配置", "", "JSON文件 (*.json)")
        if not file_path:
            return
 
        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.base_dir, path)
                    base_screenshots_path = os.path.join(self.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
 
            step = Step(**step_data)
            self.steps.append(step)
 
        self.hotkey = config.get("hotkey", "F12")
        self.hotkey_preset.setCurrentText(self.hotkey)
        self.setup_global_hotkey()
 
        loop_type = config.get("loop_type", 0)
        if loop_type == 0:
            self.radio_infinite.setChecked(True)
        else:
            self.radio_count.setChecked(True)
 
        self.loop_spin.setValue(config.get("loop_count", 1000))
 
        offset_enabled = config.get("offset_enabled", False)
        self.offset_checkbox.setChecked(offset_enabled)
        self.offset_x_spin.setValue(config.get("offset_x", 5))
        self.offset_y_spin.setValue(config.get("offset_y", 5))
        self.toggle_offset(Qt.Checked if offset_enabled else Qt.Unchecked)
 
        random_delay_enabled = config.get("random_delay_enabled", False)
        self.random_delay_checkbox.setChecked(random_delay_enabled)
        self.random_delay_min_spin.setValue(config.get("random_delay_min", 100))
        self.random_delay_max_spin.setValue(config.get("random_delay_max", 1000))
        self.toggle_random_delay(Qt.Checked if random_delay_enabled else Qt.Unchecked)
 
        self.update_steps_table()
        QMessageBox.information(self, "成功", "配置加载成功!")
 
    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()
        event.accept()
 
 
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 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:
            if not self.parent.steps:
                self.finished.emit()
                return
 
            while (self.current_loop < self.loop_count or self.loop_count == 999999) and not self.is_stopped:
                self.current_loop += 1
 
                while self.current_step_index < len(self.parent.steps) 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.current_step_index >= len(self.parent.steps):
                        break
 
                    step = self.parent.steps[self.current_step_index]
 
                    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' and step.click_type == 'jump':
                        if target_pos is not None and step.jump_to is not None:
                            self.current_step_index = step.jump_to
                            self.step_completed.emit(self.current_step_index)
                            time.sleep(total_wait / 1000.0)
                            continue
 
                    if target_pos is not None and step.click_type != 'jump':
                        self.click_target(target_pos, step)
                    elif target_pos is None and step.click_type != 'jump':
                        print(f"步骤 {self.current_step_index + 1}: 未找到目标,跳过")
 
                    time.sleep(total_wait / 1000.0)
 
                    if step.click_type != 'jump' or (step.click_type == 'jump' and target_pos is None):
                        self.current_step_index += 1
 
                    self.step_completed.emit(self.current_step_index)
 
                if not self.is_stopped:
                    self.current_step_index = 0
 
            self.finished.emit()
 
        except Exception as e:
            self.error.emit(str(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
 
        # &#9989; 关键修复:支持中文路径的读取方式
        try:
            # 使用 numpy 读取文件,避免中文路径问题
            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]
            return (max_loc[0] + w // 2, max_loc[1] + h // 2)
 
        return None
 
    def click_target(self, target_pos, step):
        x, y = target_pos
 
        if step.accept_offset and self.parent.offset_checkbox.isChecked():
            x += random.randint(
                -self.parent.offset_x_spin.value(),
                self.parent.offset_x_spin.value()
            )
            y += random.randint(
                -self.parent.offset_y_spin.value(),
                self.parent.offset_y_spin.value()
            )
 
        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_())

软件下载

下载权限
查看
  • 免费下载
    评论并刷新后下载
    登录后下载
  • {{attr.name}}:
您当前的等级为
登录后免费下载登录 小黑屋反思中,不准下载! 评论后刷新页面下载评论 支付以后下载 请先登录 您今天的下载次数(次)用完了,请明天再来 支付积分以后下载立即支付 支付以后下载立即支付 您当前的用户组不允许下载升级会员
您已获得下载权限 您可以每天下载资源次,今日剩余
本文链接:Click here to view the current URL

声明:本站为个人非盈利博客,资源均网络收集且免费分享无限制无需登录。资源仅供测试学习,请于24小时内删除,任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集。请支持正版!如若侵犯了您的合法权益,可联系我们处理。

给TA打赏
共{{data.count}}人
人已打赏
Windows软件

VMware Workstation Pro 26H1 精简特别版,免费商用级虚拟机,免激活、解锁 Mac OS 客户机与反检测配置指南

2026-5-20 14:40:59

软件

海狸:开源社交软件,企业级即时通讯系统

2025-6-9 13:52:47

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索