Bootstrap

yolo自动化项目实例解析(八)自建UI-键鼠录制回放

项目中关于键鼠的操作,不像我们之前自动化那样一步一步去定义的,而是用C++写了一个记录键鼠的操作,通过回放的方法来实现的

一、通讯系统

1、创建websocket服务器

首先通过事件循环asyncio 和websockets,创建一个持久化的服务端进程,允许客户端来请求

 vi wss.py

# -*- coding: utf-8 -*-
import asyncio
import concurrent.futures
import threading
import time
import websockets

# 存储所有已连接的客户端
connected_clients = []


# 处理客户端请求
async def handle_client(websocket):
    print("客户端连接成功")
    connected_clients.append(websocket)
    try:
        while True:
            message = await websocket.recv()
            if message is None:
                break
            print(f"客户端请求的消息: {message}")

            # 假设需要回应客户端
            await websocket.send(f"{message}")

    except websockets.exceptions.ConnectionClosed:
        print("Client 连接断开")

    finally:
        # 确保移除已断开连接的客户端
        connected_clients.remove(websocket)


async def server_main():
    # 创建 WebSocket 服务器
    server = await websockets.serve(handle_client, "localhost", 29943)
    print("启动服务端成功")

    try:
        # 等待服务器关闭
        await asyncio.Future()
    except asyncio.CancelledError:
        print("WebSocket server was cancelled.")
    except Exception as e:
        print(f"An error occurred: {e}")


import asyncio

# 创建一个新的事件循环
loop = asyncio.new_event_loop()

# 启动 WebSocket 服务器
try:
    loop.run_until_complete(server_main())

except KeyboardInterrupt:
    # 当用户按下 Ctrl+C 时会引发此异常。通常用来处理用户中断程序的情况。
    print("Stopping WebSocket server...")

finally:
    # 关闭事件循环
    loop.close()

说明

import asyncio
import concurrent.futures
import threading
import time
import websockets

# 存储所有已连接的客户端
connected_clients = []

#处理客户端请求
async def handle_client(websocket):
    print("客户端连接成功")
    connected_clients.append(websocket)
    try:
        while True:
            message = await websocket.recv()
            if message is None:
                break
            print(f"客户端请求的消息: {message}")

            # 假设需要回应客户端
            await websocket.send(f"{message}")

    except websockets.exceptions.ConnectionClosed:
        print("Client 连接断开")

    finally:
        # 确保移除已断开连接的客户端
        connected_clients.remove(websocket)


async def server_main():
    '''

    websockets.serve:这个函数用于创建一个 WebSocket 服务器,它返回一个协程对象。
    await:等待这个协程对象完成。实际上,websockets.serve 创建了服务器并立即返回,不会等待服务器完全启动,因此这里的 await 主要是用来确保协程函数能够正确执行并返回结果。
    创建服务器:这个步骤创建了一个监听在本地地址 localhost 的端口 29943 上的 WebSocket 服务器,并指定了处理客户端连接的回调函数 handle_client。

    :return:
    '''

    # 创建 WebSocket 服务器
    #当有客户端连接到服务端的时候,触发handle_client 处理请求
    server = await websockets.serve(handle_client, "localhost", 29943)
    print("启动服务端成功")


    try:
        # 等待服务器关闭
        # asyncio.Future 会开启Future协程, 通过await异步等待他完成
        # 但是因为没有设置完成条件,所以只会在中断或错误的时候退出,期间一致保持接收客户端请求
        await asyncio.Future()

    except asyncio.CancelledError:
        print("WebSocket server was cancelled.")
    except Exception as e:
        print(f"An error occurred: {e}")


import asyncio

# 创建一个新的事件循环
loop = asyncio.new_event_loop()

# 启动 WebSocket 服务器
try:
    #这个方法会启动事件循环并一直运行,直到提供的协程函数(在这里是
    #server_main())完成。如果协程函数中抛出了任何异常,run_until_complete()
    #会将这个异常重新抛出,这样就可以在try-except 块中捕获并处理它
    loop.run_until_complete(server_main())

except KeyboardInterrupt:
    #当用户按下 Ctrl+C 时会引发此异常。通常用来处理用户中断程序的情况。
    print("Stopping WebSocket server...")

finally:
    #close():这个方法用来关闭事件循环。关闭事件循环可以释放相关资源,并确保所有任务都已经完成或被适当地取消。
    #finally 块:无论前面的
    #try 块是否抛出异常,finally 块中的代码都会被执行。这确保了即使在发生异常或用户中断的情况下,事件循环也会被正确关闭

    # 关闭事件循环
    loop.close()

2、客户端请求测试

vi wsstest.py

# -*- coding: utf-8 -*
import asyncio
import websockets


async def client():
    uri = "ws://localhost:29943"
    async with websockets.connect(uri) as websocket:
        print("已连接到服务端")

        # 发送消息给服务端
        await websocket.send("我是客户端发送的消息")
        print("已发送消息到服务端")

        # 接收服务端返回的消息
        response = await websocket.recv()
        print(f"服务端消息返回: {response}")


async def main():
    await client()


# 初始化事件循环
loop = asyncio.get_event_loop()

# 启动客户端
try:
    loop.run_until_complete(main())
except KeyboardInterrupt:
    print("Stopping WebSocket client...")
finally:
    # 关闭事件循环
    loop.close()

 

服务端返回

启动服务端成功


客户端连接成功
客户端请求的消息: 我是客户端发送的消息
Client 连接断开

客户端返回

已连接到服务端
已发送消息到服务端
服务端消息返回: 我是客户端发送的消息

3、服务端--守护进程运行

上面服务端和客户端正常用起来看着没什么问题,但是我们主要是在ui上面用的,所有东西必须都是异步,不能因为开个服务端就把主进程给占用了,这里服务端做下修改

下面改用threading.Thread 以协程形式运行服务端进程,并将该协程设置为守护进程模式,只有当主进程结束时,才会结束服务端, 方便测试我们先在结尾加个input作为程序的主进程(不退出或者指定方法退出的都行)

 vi wss.py

# -*- coding: utf-8 -*
import asyncio
import concurrent.futures
import sys
import threading
import time
import websockets

# 存储所有已连接的客户端
connected_clients = []

#处理客户端请求
async def handle_client(websocket):
    print("客户端连接成功")
    connected_clients.append(websocket)
    try:
        while True:
            message = await websocket.recv()
            if message is None:
                break
            print(f"客户端请求的消息: {message}")

            # 假设需要回应客户端
            await websocket.send(f"{message}")

    except websockets.exceptions.ConnectionClosed:
        print("Client 连接断开")

    finally:
        # 确保移除已断开连接的客户端
        connected_clients.remove(websocket)

async def server_main():

    server = await websockets.serve(handle_client, "localhost", 29943, max_size=1024 * 1024 * 10)



    #搭配协程用
    # 等待 server 对象的 wait_closed 方法返回的一个协程完成。asyncio.gather 是一个用来并发运行多个异步任务的函数
    # 并且它可以返回这些任务的结果。在这个特定的情况下,gather 只接收了一个任务 server.wait_closed()
    # 因此它实际上是等待这个特定的任务完成
    # 等待 server 对象的 wait_closed 方法返回的一个协程完成。
    try:
        await asyncio.gather(server.wait_closed())
    except asyncio.CancelledError:
        print("WebSocket server was cancelled.")
    except Exception as e:
        print(f"An error occurred: {e}")



import asyncio

# 创建一个新的事件循环
loop = asyncio.new_event_loop()

#以协程非主进程形式运行
t = threading.Thread(target=loop.run_until_complete, args=(server_main(),))

#协程以守护进程运行
python_var = sys.version_info[1]
if python_var > 8:
    t.daemon = True
else:
    t.setDaemon(True)
t.start()

print("服务端协程已守护进程模式运行,下面的代码需要去运行其他的主进程, 如果没有进程则会退出")
print("这个程序将在未来作为一个协程启动,而非主进程形式")
print("没有前台进程的话直接跑的话,主进程会结束,当主进程结束时,守护进程也会关闭")

#主进程(这个我们后面是要把input改为我们ui中的某个触发点的,这里input只是用来演示)
input("回车退出")

4、添加键鼠录制插件

如果是本地主机,可能会报毒给删了,看情况自己做隔离,路径datas/JiaoBen/xxx.exe
exe文件
https://download.csdn.net/download/qq_42883074/89820151

源码
https://download.csdn.net/download/qq_42883074/89858644

5、部署插件服务

首先我们要先将录制的插件服务拉起来,通过subprocess.Popen('"' + "./datas/jiaoben/xxx.exe" + '" "' + "29943" + '"') 在启动插件的时候指定本地服务端端口

wss.py

async def server_main():

    server = await websockets.serve(handle_client, "localhost", 29943, max_size=1024 * 1024 * 10)

    #添加下面这个
    #运行插件服务
    import subprocess
    try:
        time.sleep(5)
        # 启动exe程序
        subprocess.Popen('"' + "./datas/jiaoben/xxx.exe" + '" "' + "29943" + '"')
        print("插件服务已经启动!")
    except:
        print("文件没找到可以能被杀毒软件干掉了 " + "./datas/jiaoben/xxx.exe")






    #搭配协程用
    try:
        await asyncio.gather(server.wait_closed())
    except asyncio.CancelledError:
        print("WebSocket server was cancelled.")
    except Exception as e:
        print(f"An error occurred: {e}")

返回信息

服务端协程已守护进程模式运行,下面的代码需要去运行其他的主进程, 如果没有进程则会退出
这个程序将在未来作为一个协程启动,而非主进程形式
没有前台进程的话直接跑的话,主进程会结束,当主进程结束时,守护进程也会关闭



回车退出插件服务已经启动!
客户端连接成功
客户端请求的消息: 是否禁止录制 真

可以看到当插件服务启动后,立刻给我们指定端口开始发送请求

这个插件本身就是支持通过按键来进行录制和回放,这里按F8其实就生效录制了,但我们没有输出所以看不出来

6、日志系统

在做服务端程序的时候,有很多时候我们都不方便直接print把信息打印出来,这里直接用日志模板,将服务端信息写到日志中

logger_module.py

import logging
import os
from logging.handlers import TimedRotatingFileHandler

# 创建全局的 logger
logger = logging.getLogger("LenRenAI")
logger.setLevel(logging.DEBUG)
# 创建一个handler,用于将日志输出到控制台
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)

log_dir = './log'
if not os.path.exists(log_dir):
    os.makedirs(log_dir)
# 创建一个handler,用于将日志输出到文件
file_handler = TimedRotatingFileHandler('./log/lanrenai.log', when='midnight', interval=1, backupCount=7)
file_handler.setLevel(logging.DEBUG)


# 定义日志消息格式
class CustomFormatter(logging.Formatter):
    FORMATS = {
        logging.DEBUG: '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        logging.INFO: '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        logging.WARNING: '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        logging.ERROR: '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        logging.CRITICAL: '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)


# 创建一个formatter格式类
formatter = CustomFormatter()
# 设置消息格式
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 将handler添加到logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)

7、添加全局变量

状态_是否回放中 = False
状态_是否暂停 = False
状态_是否开始录制 = False
状态_是否禁止录制 = True
状态_开伞时间 = -1
录制_当前脚本名 = ""
录制_脚本文本 = ""
录制_path = ""
录制_当前任务索引 = -1

8、服务端与插件通讯交互

我们在项目中是无法直接判断插件的运行情况的,只能通过定义变量的形式保存在本地作为开关,当请求发生变化的时候,对插件发送请求信息,然后去变更本地的变量配置,进而通过对本地变量的配置来判断插件状态

 wss.py


# 处理客户端请求
from logger_module import logger
import state
async def handle_client(websocket):
    global new_msg
    try:
        # 发送连接成功消息
        await websocket.send("连接成功")
        # 添加新连接的客户端到集合中
        connected_clients.append(websocket)
        while True:
            # 接收客户端发送的消息
            message = await websocket.recv()
            new_msg = message
            # print(f"收到消息:{message}")
            # 处理接收到的消息
            if message == "是否回放中 真":
                state.状态_是否回放中 = True
            elif message == "是否回放中 假":
                state.状态_是否回放中 = False
            elif message == "是否暂停 真":
                state.状态_是否暂停 = True
            elif message == "是否暂停 假":
                state.状态_是否暂停 = False
            elif message == "是否开始录制 假":
                state.状态_是否开始录制 = False
            elif message == "是否开始录制 真":
                if state.状态_是否禁止录制 == False:
                    logger.info("请按F8结束录制")
                    # state.QT_信号.mysig_tishi.emit(f"请按F8结束录制")
                    state.状态_是否开始录制 = True
            elif message == "是否禁止录制 假":
                state.状态_是否禁止录制 = False
            elif message == "是否禁止录制 真":
                state.状态_是否禁止录制 = True
            elif message[:5] == "录制的脚本":
                state.录制_脚本文本 = message[6:]

                # 保存文件
                if state.录制_脚本文本 != "":
                    logger.info(state.录制_脚本文本)
                    with open("1111.txt", "w", encoding="gbk", newline='') as f:
                        f.write(state.录制_脚本文本)

                    logger.info("录制完毕!")





    finally:
        # 客户端断开连接后,将其移出集合
        connected_clients.remove(websocket)


9、添加发送请求函数

我们服务端是以一个事件循环的形式去运行的,如果我们想要对其中的请求连接做某项操作,就需要有一个外部能够调用的函数去对事件信息中的客户端(插件) 发送具体的消息,让插件知道该做什么了(录制、回放)

wss.py

#外部函数
def send_msg(msg="是否回放#@@#假"):
    '''
    给躺宝发送指令
    全部指令  :
    是否回放#@@#假  /真
    是否暂停#@@#假  /真
    是否开始录制#@@#假 /真
    是否禁止录制#@@#假 /真
    解析脚本#@@#jiaoben"  jiaoben就是录制的脚本文本 不是文件 是直接文字
    全局hwnd#@@#12345   12345就是游戏的窗口句柄
    :param msg: 指令 用 #@@# 分割
    :return:
    '''

    asyncio.run_coroutine_threadsafe(send_to_client(-1, msg), loop)


#时间循环内发送请求
async def send_to_client(client_id, message):
    if len(connected_clients) > 0:
        # 查找指定的客户端
        await connected_clients[client_id].send(message)

下面的send_to_client函数是async,处于事件循环中的,相当于是拿着插件客户端的请求连接,然后通过send将消息 (msg="是否回放#@@#假") 发送给插件,让他做对应的操作

10、服务端全量代码

# -*- coding: utf-8 -*
import asyncio
import concurrent.futures
import sys
import threading
import time
import websockets



# 存储所有已连接的客户端
connected_clients = []

# 处理客户端请求
from logger_module import logger
import state
async def handle_client(websocket):
    global new_msg
    try:
        # 发送连接成功消息
        await websocket.send("连接成功")
        # 添加新连接的客户端到集合中
        connected_clients.append(websocket)
        while True:
            # 接收客户端发送的消息
            message = await websocket.recv()
            new_msg = message
            # print(f"收到消息:{message}")
            # 处理接收到的消息
            if message == "是否回放中 真":
                state.状态_是否回放中 = True
            elif message == "是否回放中 假":
                state.状态_是否回放中 = False
            elif message == "是否暂停 真":
                state.状态_是否暂停 = True
            elif message == "是否暂停 假":
                state.状态_是否暂停 = False
            elif message == "是否开始录制 假":
                state.状态_是否开始录制 = False
            elif message == "是否开始录制 真":
                if state.状态_是否禁止录制 == False:
                    logger.info("请按F8结束录制")
                    # state.QT_信号.mysig_tishi.emit(f"请按F8结束录制")
                    state.状态_是否开始录制 = True
            elif message == "是否禁止录制 假":
                state.状态_是否禁止录制 = False
            elif message == "是否禁止录制 真":
                state.状态_是否禁止录制 = True
            elif message[:5] == "录制的脚本":
                state.录制_脚本文本 = message[6:]

                # 保存文件
                if state.录制_脚本文本 != "":
                    logger.info(state.录制_脚本文本)
                    with open("1111.txt", "w", encoding="gbk", newline='') as f:
                        f.write(state.录制_脚本文本)

                    logger.info("录制完毕!")





    finally:
        # 客户端断开连接后,将其移出集合
        connected_clients.remove(websocket)
#外部函数
def send_msg(msg="是否回放#@@#假"):
    '''
    给躺宝发送指令
    全部指令  :
    是否回放#@@#假  /真
    是否暂停#@@#假  /真
    是否开始录制#@@#假 /真
    是否禁止录制#@@#假 /真
    解析脚本#@@#jiaoben"  jiaoben就是录制的脚本文本 不是文件 是直接文字
    全局hwnd#@@#12345   12345就是游戏的窗口句柄
    :param msg: 指令 用 #@@# 分割
    :return:
    '''

    asyncio.run_coroutine_threadsafe(send_to_client(-1, msg), loop)


#时间循环内发送请求
async def send_to_client(client_id, message):
    if len(connected_clients) > 0:
        # 查找指定的客户端
        await connected_clients[client_id].send(message)

async def server_main():
    server = await websockets.serve(handle_client, "localhost", 29943, max_size=1024 * 1024 * 10)

    # 添加下面这个
    # 运行插件服务
    import subprocess
    try:
        time.sleep(5)
        # 启动exe程序
        subprocess.Popen('"' + "./datas/jiaoben/xxx.exe" + '" "' + "29943" + '"')
        print("插件服务已经启动!")
    except:
        print("文件没找到可以能被杀毒软件干掉了 " + "./datas/jiaoben/xxx.exe")

    # 搭配协程用
    try:
        await asyncio.gather(server.wait_closed())
    except asyncio.CancelledError:
        print("WebSocket server was cancelled.")
    except Exception as e:
        print(f"An error occurred: {e}")


import asyncio

# 创建一个新的事件循环
loop = asyncio.new_event_loop()

#以协程非主进程形式运行
t = threading.Thread(target=loop.run_until_complete, args=(server_main(),))

#协程以守护进程运行
python_var = sys.version_info[1]
if python_var > 8:
    t.daemon = True
else:
    t.setDaemon(True)
t.start()

print("服务端协程已守护进程模式运行,下面的代码需要去运行其他的主进程, 如果没有进程则会退出")
print("这个程序将在未来作为一个协程启动,而非主进程形式")
print("没有前台进程的话直接跑的话,主进程会结束,当主进程结束时,守护进程也会关闭")

#主进程(这个我们后面是要把input改为我们ui中的某个触发点的,这里input只是用来演示)
#input("回车退出")

 返回

服务端协程已守护进程模式运行,下面的代码需要去运行其他的主进程, 如果没有进程则会退出
这个程序将在未来作为一个协程启动,而非主进程形式
没有前台进程的话直接跑的话,主进程会结束,当主进程结束时,守护进程也会关闭

Process finished with exit code 0

因为我们是从外部调用事件循环,我们要先将服务端中的那个input主进程去掉,防止调用的时候input阻塞还需要按回车

11、模拟录制键鼠

from wss import *


#打开插件的录制功能
input("###########是否开启禁止录制\n")
send_msg("是否禁止录制#@@#假")   #允许录制


input("########回车开始录制\n")
send_msg("是否开始录制#@@#真")  #开始录制


input("########回车结束录制\n")
send_msg("是否开始录制#@@#假")  #关闭录制
send_msg("是否禁止录制#@@#真")  #开启禁止

 第一次回车取消禁止录制,第二次回车开启录制功能,但需要在按一次F8去激活插件中的录制功能,再按一次F8停止录制并返回消息给服务端(这步可以反复操作),第三次回车,关闭录制功能

 返回

C:\Users\Administrator\PycharmProjects\yolo8test\.venv\Scripts\python.exe C:\Users\Administrator\PycharmProjects\yolo8test\test\test.py 
服务端协程已守护进程模式运行,下面的代码需要去运行其他的主进程, 如果没有进程则会退出
这个程序将在未来作为一个协程启动,而非主进程形式
没有前台进程的话直接跑的话,主进程会结束,当主进程结束时,守护进程也会关闭
回车退出插件服务已经启动!
收到消息:是否禁止录制 真

###########是否开启禁止录制

2024-10-15 17:21:10,416 - LenRenAI - INFO - 请求对插件客户端发送请求: 是否禁止录制#@@#假
########回车开始录制
收到消息:是否禁止录制 假
收到消息:是否禁止录制 假

2024-10-15 17:21:14,896 - LenRenAI - INFO - 请求对插件客户端发送请求: 是否开始录制#@@#真
2024-10-15 17:21:14,898 - LenRenAI - INFO - 请按F8结束录制
########回车结束录制
收到消息:是否开始录制 真
2024-10-15 17:21:15,189 - LenRenAI - INFO - 请按F8结束录制
收到消息:是否开始录制 真
收到消息:是否开始录制 假
收到消息:录制的脚本 ##测试脚本##
鼠标初始 413 935
等待键 0.0229151
等待鼠 0.0229289

2024-10-15 17:21:19,095 - LenRenAI - INFO - ##测试脚本##
鼠标初始 413 935
等待键 0.0229151
等待鼠 0.0229289

2024-10-15 17:21:19,095 - LenRenAI - INFO - 录制完毕!
收到消息:是否开始录制 真
2024-10-15 17:21:20,297 - LenRenAI - INFO - 请按F8结束录制
收到消息:是否开始录制 假
收到消息:录制的脚本 ##测试脚本##
鼠标初始 413 935
等待键 3.993411
等待鼠 3.993423

2024-10-15 17:21:24,202 - LenRenAI - INFO - ##测试脚本##
鼠标初始 413 935
等待键 3.993411
等待鼠 3.993423

2024-10-15 17:21:24,202 - LenRenAI - INFO - 录制完毕!
收到消息:是否开始录制 真
2024-10-15 17:21:33,517 - LenRenAI - INFO - 请按F8结束录制
收到消息:是否开始录制 假
收到消息:录制的脚本 ##测试脚本##
鼠标初始 625 914
等待鼠 1.1647663
鼠标 0 0 21316 55553 0
等待鼠 0.0002612
鼠标 0 0 21282 55553 0
等待鼠 0.0398962
鼠标 0 0 21248 55675 0
等待鼠 0.0604424
鼠标 0 0 21248 55735 0
等待鼠 0.0098287
鼠标 0 0 21282 55735 0
等待鼠 0.0251939
鼠标 0 0 21350 55675 0
等待鼠 0.0002929
鼠标 0 0 21486 55432 0
等待鼠 0.0195504
鼠标 0 0 21623 55189 0
等待鼠 0.4348741
鼠标 0 0 21657 55129 0
等待鼠 0.0148643
鼠标 0 0 21725 55007 0
等待鼠 0.0182851
鼠标 0 0 21760 55007 0
等待鼠 0.1272844
鼠标 1 1 21760 55007 0
等待鼠 0.0874505
鼠标 1 2 21760 55007 0
等待鼠 0.1222998
鼠标 0 0 21794 55007 0
等待键 3.3952953
等待鼠 1.2700347

2024-10-15 17:21:47,638 - LenRenAI - INFO - ##测试脚本##
鼠标初始 625 914
等待鼠 1.1647663
鼠标 0 0 21316 55553 0
等待鼠 0.0002612
鼠标 0 0 21282 55553 0
等待鼠 0.0398962
鼠标 0 0 21248 55675 0
等待鼠 0.0604424
鼠标 0 0 21248 55735 0
等待鼠 0.0098287
鼠标 0 0 21282 55735 0
等待鼠 0.0251939
鼠标 0 0 21350 55675 0
等待鼠 0.0002929
鼠标 0 0 21486 55432 0
等待鼠 0.0195504
鼠标 0 0 21623 55189 0
等待鼠 0.4348741
鼠标 0 0 21657 55129 0
等待鼠 0.0148643
鼠标 0 0 21725 55007 0
等待鼠 0.0182851
鼠标 0 0 21760 55007 0
等待鼠 0.1272844
鼠标 1 1 21760 55007 0
等待鼠 0.0874505
鼠标 1 2 21760 55007 0
等待鼠 0.1222998
鼠标 0 0 21794 55007 0
等待键 3.3952953
等待鼠 1.2700347

2024-10-15 17:21:47,639 - LenRenAI - INFO - 录制完毕!
收到消息:是否开始录制 真
2024-10-15 17:21:47,939 - LenRenAI - INFO - 请按F8结束录制


收到消息:是否开始录制 假
收到消息:是否禁止录制 真
2024-10-15 17:22:08,845 - LenRenAI - INFO - 请求对插件客户端发送请求: 是否开始录制#@@#假
2024-10-15 17:22:08,847 - LenRenAI - INFO - 请求对插件客户端发送请求: 是否禁止录制#@@#真

Process finished with exit code 0

操作记录

鼠标初始 625 914
等待鼠 1.1647663
鼠标 0 0 21316 55553 0
等待鼠 0.0002612
鼠标 0 0 21282 55553 0
等待鼠 0.0398962
鼠标 0 0 21248 55675 0
等待鼠 0.0604424
鼠标 0 0 21248 55735 0
等待鼠 0.0098287
鼠标 0 0 21282 55735 0
等待鼠 0.0251939
鼠标 0 0 21350 55675 0
等待鼠 0.0002929
鼠标 0 0 21486 55432 0
等待鼠 0.0195504
鼠标 0 0 21623 55189 0
等待鼠 0.4348741
鼠标 0 0 21657 55129 0
等待鼠 0.0148643
鼠标 0 0 21725 55007 0
等待鼠 0.0182851
鼠标 0 0 21760 55007 0
等待鼠 0.1272844
鼠标 1 1 21760 55007 0
等待鼠 0.0874505
鼠标 1 2 21760 55007 0
等待鼠 0.1222998
鼠标 0 0 21794 55007 0
等待键 3.3952953
等待鼠 1.2700347

12、键盘鼠标监听

上面使用时发现一个问题,我们好像无法观测他到底开没开始录制,按下F8没有任何提示,录制完成了也不知道从哪里整的,我们先做一个键盘按键监听器,监听我们确实按下按键并且开始录制了

# -*- coding: gbk -*-
import time

from pynput import keyboard

def on_press(key):
    # 这里写处理按键的逻辑
    print(f'{key} pressed')
    if key == keyboard.Key.esc:  # 如果按下Esc键,则停止监听
        return False

def on_release(key):
    # 可选:处理按键释放的逻辑
    print(f'{key} released')

# 使用上下文管理器来创建一个监听器
with keyboard.Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    # 监听器在此范围内活动
    # 其他代码可以在这里执行
    print("现在开始监听键盘事件,请按Esc键退出。")
    try:
        # 无限循环,保持程序运行
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        # 捕获Ctrl+C中断信号
        print("\n用户中断了程序。")

13、添加监听

# -*- coding: gbk -*-
import time
from pynput import keyboard
import state  # 假设 state 模块已经定义并包含相关变量
from wss import send_msg  # 假设 wss 模块已经定义并包含 send_msg 函数



# 检查按键
def on_press(key):
    global stop_listener  # 声明 stop_listener 为全局变量

    # 如果按下 Esc,则视为停止
    # print(f'{key} pressed')
    if key == keyboard.Key.esc:  # 如果按下Esc键,则设置停止标志
        send_msg("是否禁止录制#@@#真")  # 禁止录制录制
        send_msg("是否开始录制#@@#假")  # 关闭录制开关
        state.录制_脚本文本 = ""
        stop_listener = True  # 设置停止标志

    # 按键 F8
    if key == keyboard.Key.media_play_pause:
        # print(key)
        print("按下F8")
        if state.状态_是否开始录制 == False:
            print("录制中,再次按下F8关闭")
            send_msg("是否禁止录制#@@#假")  #取消禁止录制
            send_msg("是否开始录制#@@#真")  #允许开始录制
            hwnd = 47449172
            send_msg(f"全局hwnd#@@#{hwnd}")

        else:
            print("录制关闭")
            send_msg("是否禁止录制#@@#真")  # 禁止录制
            send_msg("是否开始录制#@@#假")  # 关闭的录制开关


# 初始化状态
send_msg("是否禁止录制#@@#真")  # 禁止录制
send_msg("是否开始录制#@@#假")  # 关闭的录制开关

# 使用上下文管理器来创建一个监听器
with keyboard.Listener(
        on_press=on_press,
) as listener:
    # 监听器在此范围内活动
    # 其他代码可以在这里执行
    print("现在开始监听键盘事件,请按Esc键退出。")
    # 判断是否停止循环
    stop_listener = False


    while not stop_listener :
        time.sleep(1)
        # print("正在监听...")


         #检查文本是否存在数据
        if state.录制_脚本文本 != "":
            # print(f"已经有数据:{state.录制_脚本文本 }")
            stop_listener = True


# 当离开 with 块时,监听器会自动停止

14、修改监听为后台运行

我们后面是要给ui用的,这里将键盘按键监听改为线程模式运行

# -*- coding: gbk -*-
import time
from pynput import keyboard
import threading
import state  # 假设 state 模块已经定义并包含相关变量
from wss import send_msg  # 假设 wss 模块已经定义并包含 send_msg 函数

# 初始化标志变量
stop_listener = False


# 检查按键
def on_press(key):
    global stop_listener  # 声明 stop_listener 为全局变量

    # 如果按下 Esc,则视为停止
    if key == keyboard.Key.esc:  # 如果按下Esc键,则设置停止标志
        send_msg("是否禁止录制#@@#真")  # 禁止录制录制
        send_msg("是否开始录制#@@#假")  # 关闭录制开关
        state.录制_脚本文本 = ""
        stop_listener = True  # 设置停止标志

    # 按键 F8
    if key == keyboard.Key.media_play_pause:
        if state.状态_是否开始录制 == False:
            print("录制中,再次按下F8关闭")
            send_msg("是否禁止录制#@@#假")  # 取消禁止录制
            send_msg("是否开始录制#@@#真")  # 允许开始录制
            hwnd = 47449172
            send_msg(f"全局hwnd#@@#{hwnd}")
        else:
            print("录制关闭")
            send_msg("是否禁止录制#@@#真")  # 禁止录制
            send_msg("是否开始录制#@@#假")  # 关闭的录制开关


# 启动按键监听器
def start_keyboard_listener():
    global stop_listener
    stop_listener = False

    def handle_on_press(key):
        on_press(key)
        if stop_listener:
            listener.stop()

    # 使用上下文管理器来创建一个监听器
    with keyboard.Listener(on_press=handle_on_press) as listener:
        print("现在开始监听键盘事件,请按Esc键退出。")
        listener.join()  # 等待监听器结束


# 启动监听器
def start_listener_thread():
    thread = threading.Thread(target=start_keyboard_listener)
    thread.start()
    return thread


# 示例使用
if __name__ == "__main__":
    # 初始化状态
    send_msg("是否禁止录制#@@#真")  # 禁止录制
    send_msg("是否开始录制#@@#假")  # 关闭的录制开关

    # 启动监听器线程
    listener_thread = start_listener_thread()

    # UI 主进程可以在这里继续运行
    while True:
        # UI 主进程逻辑
        time.sleep(1)
        if state.录制_脚本文本 != "":
            print(f"已经有数据:{state.录制_脚本文本}")
            break

    # 如果需要停止监听器
    # listener_thread.join()

15、服务端全量

import asyncio
import concurrent.futures
import os
import sys
import threading
import time
import websockets

import state

# 存储所有已连接的客户端
connected_clients = []

# 处理客户端请求
# 处理客户端请求
from logger_module import logger


async def handle_client(websocket, path):
    global new_msg
    try:
        # 发送连接成功消息
        await websocket.send("连接成功")
        # 添加新连接的客户端到集合中
        connected_clients.append(websocket)
        while True:
            # 接收客户端发送的消息
            message = await websocket.recv()
            new_msg = message
            # print(f"收到消息:{message}")
            # 处理接收到的消息
            if message == "是否回放中 真":
                state.状态_是否回放中 = True
            elif message == "是否回放中 假":
                state.状态_是否回放中 = False
            elif message == "是否暂停 真":
                state.状态_是否暂停 = True
            elif message == "是否暂停 假":
                state.状态_是否暂停 = False
            elif message == "是否开始录制 假":
                state.状态_是否开始录制 = False
            elif message == "是否开始录制 真":
                if state.状态_是否禁止录制 == False:
                    logger.info("请按F8结束录制")
                    # state.QT_信号.mysig_tishi.emit(f"请按F8结束录制")
                    state.状态_是否开始录制 = True
            elif message == "是否禁止录制 假":
                state.状态_是否禁止录制 = False
            elif message == "是否禁止录制 真":
                state.状态_是否禁止录制 = True
            elif message[:5] == "录制的脚本":
                state.录制_脚本文本 = message[6:]





    finally:
        # 客户端断开连接后,将其移出集合
        connected_clients.remove(websocket)


async def server_main():
    server = await websockets.serve(handle_client, "localhost", 29943, max_size=1024 * 1024 * 10)

    # 添加下面这个
    # 运行插件服务
    import subprocess
    try:

        # 启动exe程序
        subprocess.Popen('"' + "./datas/jiaoben/xxx.exe" + '" "' + "29943" + '"')
        print("插件服务已经启动! 按下F8开始录制")
    except:
        print("文件没找到可以能被杀毒软件干掉了 " + "./datas/jiaoben/xxx.exe")

    try:
        await asyncio.gather(server.wait_closed())
    except asyncio.CancelledError:
        print("WebSocket server was cancelled.")
    except Exception as e:
        print(f"An error occurred: {e}")


def send_msg(msg="是否回放#@@#假"):
    '''
    给躺宝发送指令
    全部指令  :
    是否回放#@@#假  /真
    是否暂停#@@#假  /真
    是否开始录制#@@#假 /真
    是否禁止录制#@@#假 /真
    解析脚本#@@#jiaoben"  jiaoben就是录制的脚本文本 不是文件 是直接文字
    全局hwnd#@@#12345   12345就是游戏的窗口句柄
    :param msg: 指令 用 #@@# 分割
    :return:
    '''
    logger.info("请求对插件客户端发送请求: " + msg)
    asyncio.run_coroutine_threadsafe(send_to_client(-1, msg), loop)


async def send_to_client(client_id, message):
    if len(connected_clients) > 0:
        # 查找指定的客户端
        await connected_clients[client_id].send(message)


import asyncio

# 创建一个新的事件循环
loop = asyncio.new_event_loop()

# 以协程非主进程形式运行
t = threading.Thread(target=loop.run_until_complete, args=(server_main(),))

# 协程以守护进程运行
python_var = sys.version_info[1]
if python_var > 8:
    t.daemon = True
else:
    t.setDaemon(True)
t.start()

# print("服务端协程已守护进程模式运行,下面的代码需要去运行其他的主进程, 如果没有进程则会退出")
# print("这个程序将在未来作为一个协程启动,而非主进程形式")
# print("没有前台进程的话直接跑的话,主进程会结束,当主进程结束时,守护进程也会关闭")

# input("回车退出")

16、客户端全量

# -*- coding: gbk -*-
import os
import time
from pynput import keyboard
import threading
import state  # 假设 state 模块已经定义并包含相关变量
from logger_module import logger
from wss import send_msg  # 假设 wss 模块已经定义并包含 send_msg 函数

# 初始化标志变量
stop_listener = False
file_lock = threading.Lock()

def safe_write_to_file(text):
    with file_lock:
        with open("1111.txt", "w", encoding="gbk", newline='', buffering=1) as f:
            f.write(text)

# 检查按键
def on_press(key):
    global stop_listener  # 声明 stop_listener 为全局变量


    # 如果按下 Esc,则视为停止
    if key == keyboard.Key.esc:  # 如果按下Esc键,则设置停止标志
        send_msg("是否禁止录制#@@#真")  # 禁止录制录制
        send_msg("是否开始录制#@@#假")  # 关闭录制开关
        state.录制_脚本文本 = ""
        stop_listener = True  # 设置停止标志

    # 按键 F8
    if key == keyboard.Key.media_play_pause:
        if state.状态_是否开始录制 == False:
            print("录制中,再次按下F8关闭")
            send_msg("是否禁止录制#@@#假")  # 取消禁止录制
            send_msg("是否开始录制#@@#真")  # 允许开始录制
            send_msg(f"全局脚本名#@@#1111.txt")  # 文件名称定义
            hwnd = 47449172
            send_msg(f"全局hwnd#@@#{hwnd}")

        elif state.状态_是否开始录制 == True:
            print("录制关闭")
            send_msg("是否禁止录制#@@#真")  # 禁止录制
            send_msg("是否开始录制#@@#假")  # 关闭的录制开关
            print(state.录制_脚本文本)
            safe_write_to_file(state.录制_脚本文本)


# 启动按键监听器
def start_keyboard_listener():
    global stop_listener
    stop_listener = False

    def handle_on_press(key):
        on_press(key)
        if stop_listener:
            listener.stop()

    # 使用上下文管理器来创建一个监听器
    with keyboard.Listener(on_press=handle_on_press) as listener:
        print("现在开始监听键盘事件,请按Esc键退出。")
        listener.join()  # 等待监听器结束


# 启动监听器
def start_listener_thread():
    thread = threading.Thread(target=start_keyboard_listener)
    thread.start()
    return thread


# 示例使用
if __name__ == "__main__":
    # 初始化状态
    send_msg("是否禁止录制#@@#真")  # 禁止录制
    send_msg("是否开始录制#@@#假")  # 关闭的录制开关

    # 启动监听器线程
    listener_thread = start_listener_thread()

    # UI 主进程可以在这里继续运行
    while True:
        # 保存文件
        time.sleep(1)


    # 如果需要停止监听器
    # listener_thread.join()

    # 脚本回放
    # if state.状态_是否回放中 == True:
    #     return
    # if not os.path.exists(state.PATH_JIAOBEN):
    #     os.makedirs(state.PATH_JIAOBEN)
    # state.状态_是否回放中 = True
    # state.状态_循环开关 = True
    # jiaoben = os.path.abspath(os.path.join(state.PATH_JIAOBEN,state.LIANZHAO ))
    # message = f"解析脚本#@@#{jiaoben}"
    # tangbaowss.send_msg(message)
    # hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)  # 替换成你实际的窗口句柄
    # set_window_activate(hwnd)
    # tangbaowss.send_msg("脚本执行#@@#1")
    # self.timer_huifang.start(200)

未完

4、初始化触发

以下4个定时器会周期性的去采集和回放键鼠操作

class MainWindow(QMainWindow, Ui_mainWindow):
    def __init__(self):

     。。。
        #4个定时器,周期进行录制和回放
        self.timer_luzhi = QTimer(self)
        self.timer_luzhi.timeout.connect(self.timeout_luzhi)

        self.timer_luzhi_chuansong = QTimer(self)
        self.timer_luzhi_chuansong.timeout.connect(self.timeout_luzhi_chuansong)

        self.timer_luzhi_lianzhao = QTimer(self)
        self.timer_luzhi_lianzhao.timeout.connect(self.timeout_luzhi_lianzhao)

        self.timer_huifang = QTimer(self)
        self.timer_huifang.timeout.connect(self.timeout_huifang)

5、定时器任务(录制、传送、连招函数、回放)

    def timeout_luzhi(self):
        if state.录制_脚本文本 != "":

            tangbaowss.send_msg("是否禁止录制#@@#真")
            state.状态_是否禁止录制 = True
            dir_ = os.path.join(state.PATH_TASK, self.datas[state.录制_当前任务索引]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)
            # 并且将jiaoben.ini文件也填入数据然后生成
            path = dir_ + "/脚本.txt"
            with open(path, "w", encoding="gbk", newline='') as f:
                f.write(state.录制_脚本文本)
            self.datas[state.录制_当前任务索引]["jiaoben"] = "脚本.txt"
            self.datas[state.录制_当前任务索引]["f_item"].bt_jiaoben.setProperty("OK_jiaoben", True)
            print(f"导入脚本成功:{path}")
            self.sg.mysig_tishi.emit(f"导入脚本成功:{path}")
            self.datas[state.录制_当前任务索引]["f_item"].bt_jiaoben.setStyleSheet("""
                                                                                    #bt_jiaoben[OK_jiaoben="true"] {
                                                                                    color: rgb(237,182,43);  
                                                                                    border-color: rgb(237,182,43); 
                                                                                    }
                                                                                 """)
            state.录制_脚本文本 = ""
            self.save_ini(dir_, self.datas[state.录制_当前任务索引])
            self.timer_luzhi.stop()

    def timeout_luzhi_chuansong(self):
        if state.录制_脚本文本 != "":
            tangbaowss.send_msg("是否禁止录制#@@#真")
            state.状态_是否禁止录制 = True
            dir_ = os.path.join(state.PATH_TASK, self.datas[state.录制_当前任务索引]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)
            # 并且将jiaoben.ini文件也填入数据然后生成
            path = dir_ + "/传送脚本.txt"
            with open(path, "w", encoding="gbk", newline='') as f:
                f.write(state.录制_脚本文本)
            self.datas[state.录制_当前任务索引]["chuansong"] = "传送脚本.txt"
            self.datas[state.录制_当前任务索引]["f_item"].bt_chuansong.setProperty("OK_chuansong", True)
            print(f"导入脚本成功:{path}")
            self.sg.mysig_tishi.emit(f"导入脚本成功:{path}")
            self.datas[state.录制_当前任务索引]["f_item"].bt_chuansong.setStyleSheet("""
                                                                                       #bt_chuansong[OK_chuansong="true"] {
                                                                                       color: rgb(237,182,43);  
                                                                                       border-color: rgb(237,182,43); 
                                                                                       }
                                                                                    """)
            state.录制_脚本文本 = ""
            self.save_ini(dir_, self.datas[state.录制_当前任务索引])
            self.timer_luzhi_chuansong.stop()

    def timeout_luzhi_lianzhao(self):
        if state.录制_脚本文本 != "":
            tangbaowss.send_msg("是否禁止录制#@@#真")
            state.状态_是否禁止录制 = True
            dir_ = os.path.abspath(state.PATH_JIAOBEN)
            if not os.path.exists(dir_):
                os.makedirs(dir_)
            # 并且将jiaoben.ini文件也填入数据然后生成
            path = os.path.join(dir_, state.LIANZHAO)
            with open(path, "w", encoding="gbk", newline='') as f:
                f.write(state.录制_脚本文本)
            state.录制_脚本文本 = ""
            print("录制并选择连招脚本成功!")
            self.sg.mysig_tishi.emit("录制并选择连招脚本成功!")
            self.save_ini_seting()
            self.timer_luzhi_lianzhao.stop()

    def timeout_huifang(self):
        if state.状态_循环开关 == False:
            state.状态_需重新传送 = False
            tangbaowss.send_msg("是否回放#@@#假")
            print("回放结束1")
            self.sg.mysig_tishi.emit("回放结束1")
            self.timer_huifang.stop()
            return
        if state.状态_是否回放中 == False:
            print("回放结束2")
            self.sg.mysig_tishi.emit("回放结束2")
            self.timer_huifang.stop()

说明

1. timeout_luzhi
    #这个函数会在定时器触发时执行,主要功能如下:
    #如果state.录制_脚本文本不为空,则表示有脚本需要保存。
    #禁止进一步录制(通过tangbaowss.send_msg("是否禁止录制#@@#真"))。
    #将脚本保存到指定目录下的脚本.txt文件中。
    #更新相关数据结构(如self.datas),并设置样式来标记脚本已成功导入。
    #清空state.录制_脚本文本。
    #停止定时器。

2. timeout_luzhi_chuansong
      #这个函数与timeout_luzhi非常相似,但是它将脚本保存到了传送脚本.txt文件中,
      #并且更新的是self.datas中的"chuansong"字段。这表明它可能专门用于处理与“传送”相关的脚本。

3. timeout_luzhi_lianzhao
         #这个函数也是类似的功能,但是它将脚本保存到了state.LIANZHAO指定的文件名中
         #并且保存到了state.PATH_JIAOBEN目录下。这表明它可能用于处理与“连招”相关的脚本


4. timeout_huifang
    #这个函数则处理回放结束的情况:
    #如果循环开关关闭,则发送“是否回放#@@#假”消息,表示停止回放。
    #更新状态,并发出提示信号。
    #如果回放状态变为False,则同样发送停止回放的消息,并停止定时器。





5、小结
   这四个函数都是定时器触发的回调函数,它们共同作用于录制和回放的过程中。
具体来说:

    当录制完成时,定时器触发timeout_luzhi、timeout_luzhi_chuansong或timeout_luzhi_lianzhao之一,将录制的脚本保存到文件,并通知系统停止录制。
    当回放结束时,定时器触发timeout_huifang,通知系统停止回放,并发出相应的信号。


这四个函数是整个系统的一部分,它们协同工作以确保录制的脚本能够正确保存,
并在回放结束后及时停止回放。这些函数通过定时器机制,
确保在适当的时候执行保存和停止操作,保证系统的稳定性和可靠性。

总的来说,在我们程序开始执行的时候,他们就会去循环检测我们文件中关于是否开启还是关闭录制、回放、传送的对应变量,如果发生变化就对我们上面的插件发送信号

小结 基本流程
具体步骤

    启动服务端:
        Python脚本启动WebSocket服务器,并监听一个端口。
        服务器准备好接收客户端连接,并处理来自客户端的消息。

    启动客户端(外部程序):
        Python脚本通过subprocess.Popen启动外部程序,并传入WebSocket服务器的端口号。
        外部程序连接到WebSocket服务器,并监听服务器的消息。

    状态变化检测:
        定时器定期检查状态变量。
        如果检测到状态变化(例如录制完成、回放结束等),则执行相应的逻辑(如保存脚本、停止录制等)。

    发送信号:
        定时器会调用tangbaowss.send_msg()函数,向WebSocket服务器发送消息。
        例如,“是否禁止录制#@@#真”、“是否回放#@@#假”等。

    处理消息:
        WebSocket服务器接收到消息后,会根据消息内容更新状态变量。
        这些状态更新可能会触发进一步的动作(例如,通知外部程序停止录制)。

6、添加菜单(文件--录制)

connect_set 函数是我们前面用来做菜单栏的函数,还是在这里整

    #绑定信号槽
    def connect_set(self):


        self.sg = MySignal()
        state.QT_信号 = self.sg  #存储本地变量,允许其他函数查询并发送


        self.menu = self.menuBar()
        # 创建一个顶级菜单
        self.menu_file = self.menu.addMenu("文件")

        self.action_addlianzhao = QAction("录制/编辑连招", self)
        self.action_addlianzhao.triggered.connect(self.hotkey_addlianzhao)
        self.menu_file.addAction(self.action_addlianzhao)


        。。。


    #添加录制函数
    def hotkey_addlianzhao(self):

        # 弹出一个选择弹窗 选项为  录制还是文件中选择
        msgBox = QMessageBox(self)
        msgBox.setWindowTitle("配置连招")  # 设置消息框标题
        msgBox.setIcon(QMessageBox.Information)

        msgBox.setText("当前连招:" + state.LIANZHAO + "\n请选择一个配置方式:")
        msgBox.addButton("录制", QMessageBox.AcceptRole)
        msgBox.addButton("回放", QMessageBox.ApplyRole)
        msgBox.addButton("文件中选择", QMessageBox.RejectRole)
        result = msgBox.exec_()
        if result == QMessageBox.AcceptRole:
            try:
                text, ok = QInputDialog.getText(self, '提示', '请输入脚本名:', text=state.LIANZHAO)
                if not ok:
                    return
                if text == "":
                    return
                elif text[-4:] != ".txt":
                    state.LIANZHAO = text + ".txt"
                else:
                    state.LIANZHAO = text
                print("可以开始录制了,按F8开始/停止")
                self.sg.mysig_tishi.emit("可以开始录制了,按F8开始/停止")
                state.状态_是否禁止录制 = False
                state.状态_是否开始录制 = False

                state.录制_脚本文本 = ""
                tangbaowss.send_msg("是否禁止录制#@@#假")
                tangbaowss.send_msg("是否开始录制#@@#假")
                tangbaowss.send_msg(f"全局脚本名#@@#{state.LIANZHAO.replace('.txt', '')}")
                hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)  # 替换成你实际的窗口句柄
                tangbaowss.send_msg(f"全局hwnd#@@#{hwnd}")
                self.timer_luzhi_lianzhao.start(200)
            except:
                pass
        elif result == QMessageBox.RejectRole:
            if state.状态_是否回放中 == True:
                return
            if not os.path.exists(state.PATH_JIAOBEN):
                os.makedirs(state.PATH_JIAOBEN)
            state.状态_是否回放中 = True
            state.状态_循环开关 = True
            jiaoben = os.path.abspath(os.path.join(state.PATH_JIAOBEN, state.LIANZHAO))
            message = f"解析脚本#@@#{jiaoben}"
            tangbaowss.send_msg(message)
            hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)  # 替换成你实际的窗口句柄
            set_window_activate(hwnd)
            tangbaowss.send_msg("脚本执行#@@#1")
            self.timer_huifang.start(200)
        elif result == QMessageBox.Warning:
            self.hotkey_setlianzhao()
    def hotkey_setlianzhao(self):
        # 弹出一个有分组框的窗口
        temp_path, ok = QFileDialog.getOpenFileName(self, "连招脚本", state.PATH_JIAOBEN, "脚本文件 (*.txt)")
        if ok:
            _, state.LIANZHAO = os.path.split(temp_path)
            dir_ = os.path.join(state.PATH_JIAOBEN, state.LIANZHAO)
            try:
                shutil.copy(temp_path, dir_)
            except:
                pass
            print("选择连招脚本成功!")
            self.save_ini_seting()




    def hotkey_setlianzhao(self):
        # 弹出一个有分组框的窗口
        temp_path, ok = QFileDialog.getOpenFileName(self, "连招脚本", state.PATH_JIAOBEN, "脚本文件 (*.txt)")
        if ok:
            _, state.LIANZHAO = os.path.split(temp_path)
            dir_ = os.path.join(state.PATH_JIAOBEN, state.LIANZHAO)
            try:
                shutil.copy(temp_path, dir_)
            except:
                pass
            print("选择连招脚本成功!")
            self.save_ini_seting()

注意,这里有个set_window_activate(hwnd)的函数是我们之前的代码moni中带的强制激活窗口,这里单开的案例应该是没有的。

7、新增信号

class MySignal(QObject):
    mySignal = pyqtSignal(int, str)  # 定义自定义信号,并指定信号参数类型
    mysig_shutdown = pyqtSignal()
    mysig_mouse_through = pyqtSignal(bool)
    mysig_tishi = pyqtSignal(str)
    mysig_show_xunlu = pyqtSignal()
    mysig_show_yolov = pyqtSignal()
    mysig_dingwei=pyqtSignal(int)
    mysig_next_pack=pyqtSignal()

8、全量代码

import configparser
import ctypes
import functools
import os
import re
import shutil
import subprocess
import sys
import threading
import time

import win32gui
# 导入PyQt5模块
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

import state
import tangbaowss
from demo1.lanrenauto.moni.moni import set_window_activate
# 导入UI文件生成的类
from ui.show import Ui_DockWidget
from ui.main import Ui_mainWindow
from ui.formpy import Ui_formpy

def extract_number(folder_name):
    numbers = re.findall(r'\d+', folder_name)
    return int(numbers[0]) if numbers else float('inf')





class FramePY(QFrame, Ui_formpy):
    def __init__(self, parent=None):
        super(QFrame, self).__init__(parent)
        self.setupUi(self)
        self.set_ui()
        self.is_zd = False
        w = int(item_width * ratio)
        h = int(item_height_min * ratio)
        self.setMinimumSize(w + int(1 * ratio), h)
        self.setMaximumSize(w, h)
        # self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint | Qt.WindowStaysOnTopHint)

    def set_ui(self):
        pass

    def mousePressEvent(self, event):
        if not self.is_zd:
            self.is_zd = not self.is_zd
            w = int(item_width * ratio)
            h = int(item_height * ratio)
            self.setMinimumSize(w + int(1 * ratio), h)
            self.setMaximumSize(w, h)
            self.bt_arg.setVisible(True)
            self.bt_opendir.setVisible(True)
            self.bt_chuansong.setVisible(True)
            self.bt_moban.setVisible(True)
            self.bt_moban_maodian.setVisible(True)
            self.bt_start.setVisible(True)
            self.bt_del.setVisible(True)
        else:
            self.is_zd = not self.is_zd
            w = int(item_width * ratio)
            h = int(item_height_min * ratio)
            self.setMinimumSize(w + int(1 * ratio), h)
            self.setMaximumSize(w, h)
            self.bt_arg.setVisible(False)
            self.bt_opendir.setVisible(False)
            self.bt_chuansong.setVisible(False)
            self.bt_moban.setVisible(False)
            self.bt_moban_maodian.setVisible(False)
            self.bt_start.setVisible(False)
            self.bt_del.setVisible(False)


class MySignal(QObject):
    mysig_show_xunlu = pyqtSignal()
    mysig_show_yolov = pyqtSignal()

    mySignal = pyqtSignal(int, str)  # 定义自定义信号,并指定信号参数类型
    mysig_shutdown = pyqtSignal()
    mysig_mouse_through = pyqtSignal(bool)
    mysig_tishi = pyqtSignal(str)

    mysig_dingwei=pyqtSignal(int)
    mysig_next_pack=pyqtSignal()


# 定义FormShow类,继承自QDockWidget和Ui_DockWidget
class FormShow(QDockWidget, Ui_DockWidget):
    def __init__(self, parent=None):
        super(QDockWidget, self).__init__(parent)  # 调用父类构造函数
        self.parent = parent  # 保存父窗口引用
        self.setParent(parent)  # 设置父窗口
        self.setupUi(self)  # 初始化UI界面
        self.set_ui()  # 自定义设置UI界面

        # 设置窗口标题
        self.setWindowTitle("检测预览")

        # 设置标签控件属性
        self.lb_yolov.setScaledContents(True)
        self.lb_yolov.setAlignment(Qt.AlignCenter)
        self.lb_xunlu.setAlignment(Qt.AlignCenter)

        # 移动窗口位置
        self.move(0, 0)

        # 设置窗口保持在最顶层
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        # 计算窗口大小
        self.window_height = int(270 * ratio)
        self.window_width = int(480 * ratio)

        # 设置窗口最小尺寸
        self.setMinimumSize(self.window_width, self.window_height * 2)

        # 连接按钮点击事件
        self.bt_jia.clicked.connect(self.clicked_jia)
        self.bt_jian.clicked.connect(self.clicked_jian)

    # 自定义UI设置
    def set_ui(self):
        pass  # 此处可添加更多UI设置

    # 按钮“加”点击事件处理
    def clicked_jia(self):
        # 如果窗口高度加上增量后超过屏幕高度,则返回
        if self.window_height + 108 * ratio > 1080 * ratio:
            return

        # 更新窗口大小
        self.window_height += int(108 * ratio)
        self.window_width += int(190 * ratio)

        # 设置标签固定高度
        self.lb_xunlu.setFixedHeight(self.window_height)

        # 根据窗口宽度调整标签位置
        if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
            self.lb_yolov.move(self.lb_xunlu.width(), 0)
        else:
            self.lb_yolov.move(0, self.lb_xunlu.height())

        # 设置标签固定大小
        self.lb_yolov.setFixedHeight(self.window_height)
        self.lb_yolov.setFixedWidth(self.window_width)

    # 按钮“减”点击事件处理
    def clicked_jian(self):
        # 如果窗口高度减去增量后小于最小高度,则返回
        if self.window_height - 108 * ratio < 270 * ratio:
            return

        # 更新窗口大小
        self.window_height -= int(108 * ratio)
        self.window_width -= int(190 * ratio)

        # 设置标签固定高度
        self.lb_xunlu.setFixedHeight(self.window_height)

        # 根据窗口宽度调整标签位置
        if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
            self.lb_yolov.move(self.lb_xunlu.width(), 0)
        else:
            self.lb_yolov.move(0, self.lb_xunlu.height())

        # 设置标签固定大小
        self.lb_yolov.setFixedHeight(self.window_height)
        self.lb_yolov.setFixedWidth(self.window_width)

    # 重写关闭事件
    def closeEvent(self, event):
        # 关闭窗口时取消主窗口上的动作选中状态
        self.parent.action_isShow.setChecked(False)

    # 重写调整大小事件
    def resizeEvent(self, event):
        # 根据窗口宽度调整标签位置
        if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
            self.lb_yolov.move(self.lb_xunlu.width(), 0)
        else:
            self.lb_yolov.move(0, self.lb_xunlu.height())


# 定义MainWindow类,继承自QMainWindow和Ui_mainWindow
class MainWindow(QMainWindow, Ui_mainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()  # 调用父类构造函数
        self.setupUi(self)  # 设置UI布局
        self.retranslateUi(self)  # 重新翻译UI组件的文字

        # 修改窗口样式为黑灰色,高亮
        self.set_ui()

        # 设置窗口标题
        self.setWindowTitle(f"修改主页标题")

        # 设置窗口尺寸
        self.resize(640, 900)

        # 设置窗口起始位置
        self.move(0, 300)

        # 创建网格布局
        self.g_box = QGridLayout()

        # 创建一个中间部件
        self.container_widget = QWidget()

        # 将网格布局设置给中间部件
        self.container_widget.setLayout(self.g_box)

        # 设置布局左上对齐
        self.g_box.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        # 设置布局边距
        self.g_box.setContentsMargins(0, 0, 0, 0)

        # 设置布局左对齐
        self.g_box.setAlignment(Qt.AlignTop)

        # 将中间部件设置为QScrollArea的子部件
        self.sa_main.setWidget(self.container_widget)

        # 获取纵向滚动条控件
        vertical_scrollbar = self.findChild(QScrollArea)
        self.v_scrollbar = vertical_scrollbar.verticalScrollBar()

        # 设置窗口保持在最顶层
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        # 初始化变量
        self.row = 0
        self.column = -1
        self.run_times = 1
        self.column_step = 310

        # 获取主窗口的状态栏对象
        self.statusbar = self.statusBar()

        # 设置状态栏文本
        self.statusbar.showMessage('欢迎使用')

        # 初始化数据列表
        self.datas = []

        # 创建一个计时器,用于延迟布局
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)  # 设置为单次触发
        self.timer.timeout.connect(self.update_layout)

        # 绑定信号槽
        self.connect_set()

        # 创建定时器  用于周期显示图片
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.on_timer_timeout)
        self.timer.start(5000)  # 每5秒触发一次

        # 添加任务包
        self.load_task_packs()

        #显示默认的任务
        self.show_tool_item(state.PATH_TASK)


        self.timer_luzhi = QTimer(self)
        self.timer_luzhi.timeout.connect(self.timeout_luzhi)

        self.timer_luzhi_chuansong = QTimer(self)
        self.timer_luzhi_chuansong.timeout.connect(self.timeout_luzhi_chuansong)
        self.timer_luzhi_lianzhao = QTimer(self)
        self.timer_luzhi_lianzhao.timeout.connect(self.timeout_luzhi_lianzhao)

        self.timer_huifang = QTimer(self)
        self.timer_huifang.timeout.connect(self.timeout_huifang)

    def load_task_packs(self):
        self.toolBar.clear()
        packs = state.PACKS_TASK.split("#@@#")
        try:
            if len(packs) >= 1:
                for pack in packs:
                    if pack != "":
                        self.add_tool_item(pack)
            else:

                self.add_tool_item(state.PACKS_TASK)
        except:
            pass

    def add_tool_item(self, dir_):

        action_item = QWidgetAction(self)
        dir_ = dir_.replace("/", "\\")

        if dir_[-1] == "\\":
            dir_ = dir_[:-1]

        # 这里把路径下的目录名取出来当作按钮名称
        bt_item = QPushButton(dir_.split("\\")[-1].strip())
        bt_item.setMaximumWidth(int(80 * ratio))

        # 读取项目的说明打印出来
        try:
            with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                txt = f.read()
        except:
            try:
                with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                    txt = f.read()
            except:
                txt = "无  \n(可以在任务包文件夹下创建一个 使用说明.txt 文件 来添加说明)"

        bt_item.setToolTip(dir_ + "\n使用说明:" + txt + "\n")
        bt_item.setStyleSheet("border: none; padding: 3px;")

        # 绑定按钮的功能
        bt_item.clicked.connect(functools.partial(self.show_tool_item, dir_))

        # 添加动作
        action_item.setDefaultWidget(bt_item)
        # 按钮添加到工具栏
        self.toolBar.addAction(action_item)

        menu = QMenu(self)

        action_menu_down = QAction('顺序 ↓', self)
        action_menu_down.triggered.connect(functools.partial(self.down_tool_item, len(self.toolBar.actions())))
        menu.addAction(action_menu_down)
        action_menu_up = QAction('顺序 ↑', self)
        action_menu_up.triggered.connect(functools.partial(self.up_tool_item, len(self.toolBar.actions())))
        menu.addAction(action_menu_up)
        action_menu_del = QAction('删除任务包', self)
        action_menu_del.triggered.connect(functools.partial(self.del_tool_item, action_item, bt_item, dir_))
        menu.addAction(action_menu_del)
        # 将菜单关联到工具栏上
        bt_item.setContextMenuPolicy(Qt.CustomContextMenu)
        bt_item.customContextMenuRequested.connect(lambda pos: menu.exec_(bt_item.mapToGlobal(pos)))
        self.change_tool_show_style(dir_)

    def del_tool_item(self, action_item, bt_item, dir_):
        self.toolBar.removeAction(action_item)
        if state.PATH_TASK.replace("/", "\\") == dir_:
            self.datas = []
            self.row = 0
            self.column = -1
            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)
        del bt_item, action_item  # 删除动作对象和bt对象

        txt_ = ""
        packs = state.PACKS_TASK.split("#@@#")

        if len(packs) >= 1:
            for pack in packs:

                if os.path.realpath(dir_) != os.path.realpath(pack) and pack != "":
                    txt_ = txt_ + pack + "#@@#"
        state.PACKS_TASK = txt_
        print(f"成功移除{dir_}任务包")
        self.sg.mysig_tishi.emit(f"成功移除{dir_}任务包")

    def down_tool_item(self, idex):
        task_list = state.PACKS_TASK.split("#@@#")
        if idex < len(task_list) - 1:
            # 将指定索引位置的成员与其前一个成员交换位置
            task_list[idex], task_list[idex + 1] = task_list[idex + 1], task_list[idex]

        state.PACKS_TASK = ""
        for item in task_list:
            if item != "":
                state.PACKS_TASK += "#@@#" + item

        self.load_task_packs()

    def up_tool_item(self, idex):
        task_list = state.PACKS_TASK.split("#@@#")
        if idex > 0:
            # 将指定索引位置的成员与其后一个成员交换位置
            task_list[idex], task_list[idex - 1] = task_list[idex - 1], task_list[idex]
        state.PACKS_TASK = ""
        for item in task_list:
            if item != "":
                state.PACKS_TASK += "#@@#" + item

        self.load_task_packs()

    def save_ini_seting(self):
        try:
            hwnd = ctypes.windll.kernel32.GetConsoleWindow()
            if hwnd:
                rect = win32gui.GetWindowRect(hwnd)
                x, y, w, h = rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]
            else:
                x, y, w, h = state.CMD_LEFT, state.CMD_TOP, state.CMD_WIDTH, state.CMD_HEIGHT

            # 创建 ConfigParser 对象
            config = configparser.ConfigParser()
            # 添加节和键-值对
            config['seting'] = {
                "GAME_TITLE": state.GAME_TITLE,
                'LIANZHAO': state.LIANZHAO,
                'LIANZHAOFUWU': state.LIANZHAOFUWU,
                'DUANGKOUHAO': state.DUANGKOUHAO,
                'PATH_TASK': state.PATH_TASK,
                'PATH_JIAOBEN': state.PATH_JIAOBEN,
                'PACKS_TASK': state.PACKS_TASK,
                'WEIGHTS': state.WEIGHTS,
                'PROVIDERS': state.PROVIDERS,
                'IMGSIZE_WIDTH': str(state.IMGSIZE_WIDTH),
                'IMGSIZE_HEIGHT': str(state.IMGSIZE_HEIGHT),
                'WINDOW_WIDTH': str(self.width()),
                'WINDOW_HEIGHT': str(self.height()),
                'WINDOW_LEFT': str(self.x()),
                'WINDOW_TOP': str(self.y()),
                'CMD_WIDTH': str(w),
                'CMD_HEIGHT': str(h),
                'CMD_LEFT': str(x),
                'CMD_TOP': str(y),
                'ON_SHUTDOWN': str(state.ON_SHUTDOWN),
                'ON_JIXING': str(state.ON_JIXING),
                'ON_NEXTPACK': str(state.ON_NEXTPACK),
                'ON_STARTWITH': str(state.ON_STARTWITH),
                'ON_LIANZHAOBUJIANCE': str(state.ON_LIANZHAOBUJIANCE),
                'TIMEOUT_DAGUAI': str(state.TIMEOUT_DAGUAI)
            }
            # 写入配置到 INI 文件
            with open("./datas/setting.ini", 'w') as configfile:
                config.write(configfile)
        except:
            pass

    def change_tool_show_style(self, dir_):
        # 获取工具栏上的所有动作
        actions_list = self.toolBar.actions()

        # 遍历处理每个动作
        for action in actions_list:
            # 在这里对每个动作进行处理

            bt_item = action.defaultWidget()
            if dir_ == bt_item.toolTip().replace("/", "\\").split("\n")[0]:
                bt_item.setStyleSheet("border-width: 1px; padding: 3px;")
            else:
                bt_item.setStyleSheet("border: none; padding: 3px;")

    def show_tool_item(self, dir_):

        self.save()
        state.PATH_TASK = dir_
        self.save_ini_seting()
        #self.update_tasks()   不知道为什么作者在这里又写了一遍,先注释了
        #self.change_tool_show_style(dir_)
        print(f"成功设置{state.PATH_TASK}为当前任务文件夹")






    def timeout_luzhi(self):
        if state.录制_脚本文本 != "":

            tangbaowss.send_msg("是否禁止录制#@@#真")
            state.状态_是否禁止录制 = True
            dir_ = os.path.join(state.PATH_TASK, self.datas[state.录制_当前任务索引]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)
            # 并且将jiaoben.ini文件也填入数据然后生成
            path = dir_ + "/脚本.txt"
            with open(path, "w", encoding="gbk", newline='') as f:
                f.write(state.录制_脚本文本)
            self.datas[state.录制_当前任务索引]["jiaoben"] = "脚本.txt"
            self.datas[state.录制_当前任务索引]["f_item"].bt_jiaoben.setProperty("OK_jiaoben", True)
            print(f"导入脚本成功:{path}")
            self.sg.mysig_tishi.emit(f"导入脚本成功:{path}")
            self.datas[state.录制_当前任务索引]["f_item"].bt_jiaoben.setStyleSheet("""
                                                                                    #bt_jiaoben[OK_jiaoben="true"] {
                                                                                    color: rgb(237,182,43);  
                                                                                    border-color: rgb(237,182,43); 
                                                                                    }
                                                                                 """)
            state.录制_脚本文本 = ""
            self.save_ini(dir_, self.datas[state.录制_当前任务索引])
            self.timer_luzhi.stop()

    def timeout_luzhi_chuansong(self):
        if state.录制_脚本文本 != "":
            tangbaowss.send_msg("是否禁止录制#@@#真")
            state.状态_是否禁止录制 = True
            dir_ = os.path.join(state.PATH_TASK, self.datas[state.录制_当前任务索引]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)
            # 并且将jiaoben.ini文件也填入数据然后生成
            path = dir_ + "/传送脚本.txt"
            with open(path, "w", encoding="gbk", newline='') as f:
                f.write(state.录制_脚本文本)
            self.datas[state.录制_当前任务索引]["chuansong"] = "传送脚本.txt"
            self.datas[state.录制_当前任务索引]["f_item"].bt_chuansong.setProperty("OK_chuansong", True)
            print(f"导入脚本成功:{path}")
            self.sg.mysig_tishi.emit(f"导入脚本成功:{path}")
            self.datas[state.录制_当前任务索引]["f_item"].bt_chuansong.setStyleSheet("""
                                                                                       #bt_chuansong[OK_chuansong="true"] {
                                                                                       color: rgb(237,182,43);  
                                                                                       border-color: rgb(237,182,43); 
                                                                                       }
                                                                                    """)
            state.录制_脚本文本 = ""
            self.save_ini(dir_, self.datas[state.录制_当前任务索引])
            self.timer_luzhi_chuansong.stop()

    def timeout_luzhi_lianzhao(self):
        if state.录制_脚本文本 != "":
            tangbaowss.send_msg("是否禁止录制#@@#真")
            state.状态_是否禁止录制 = True
            dir_ = os.path.abspath(state.PATH_JIAOBEN)
            if not os.path.exists(dir_):
                os.makedirs(dir_)
            # 并且将jiaoben.ini文件也填入数据然后生成
            path = os.path.join(dir_, state.LIANZHAO)
            with open(path, "w", encoding="gbk", newline='') as f:
                f.write(state.录制_脚本文本)
            state.录制_脚本文本 = ""
            print("录制并选择连招脚本成功!")
            self.sg.mysig_tishi.emit("录制并选择连招脚本成功!")
            self.save_ini_seting()
            self.timer_luzhi_lianzhao.stop()

    def timeout_huifang(self):
        if state.状态_循环开关 == False:
            state.状态_需重新传送 = False
            tangbaowss.send_msg("是否回放#@@#假")
            print("回放结束1")
            self.sg.mysig_tishi.emit("回放结束1")
            self.timer_huifang.stop()
            return
        if state.状态_是否回放中 == False:
            print("回放结束2")
            self.sg.mysig_tishi.emit("回放结束2")
            self.timer_huifang.stop()












    def save(self):

        for idex in range(len(self.datas)):
            self.returnPressed_name(idex)
        # 重写closeEvent方法,在窗口关闭时调用quit()退出应用程序

        for i, data in enumerate(self.datas):
            dir_ = os.path.join(state.PATH_TASK, data["name"])
            self.save_ini(dir_, data)
        self.update_tasks()
        self.save_ini_seting()

    def update_tasks(self):
        try:
            self.datas = []
            self.row = 0
            self.column = -1

            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)
            # 遍历文件夹下有哪些目录
            # 判断这个路径是否存在
            if not os.path.exists(state.PATH_TASK):
                state.PATH_TASK = "./datas/Task/"

            # 创建 ConfigParser 对象
            config_main = configparser.ConfigParser()
            # 加载 INI 文件

            config_main.read(os.path.join(state.PATH_TASK, "细节参数.ini"))
            self.run_times = int(config_main.get('seting', 'run_times', fallback='1'))
            self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")

            # 获取文件夹列表
            folders = [item for item in os.listdir(state.PATH_TASK) if
                       os.path.isdir(os.path.join(state.PATH_TASK, item))]
            print(folders)
            # 将文件夹名称中的数字提取出来,并按照数字大小排序
            sorted_folders = sorted(folders, key=lambda x: extract_number(x))
            for item in sorted_folders:
                item_path = os.path.join(state.PATH_TASK, item)
                if os.path.isdir(item_path):
                    # 创建 ConfigParser 对象
                    config = configparser.ConfigParser()
                    # 加载 INI 文件
                    config.read(os.path.join(item_path, "jiaoben.ini"))
                    if config.get('seting', 'type', fallback='1') == "2":
                        # 副本任务
                        # self.add_taskfuben(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "3":
                        # 脚本任务
                        # self.add_taskjiaoben(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "4":
                        # 脚本任务
                        # self.add_xuanjue(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "5":
                        # 脚本任务
                        # self.add_xuanlianzhao(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "6":
                        self.add_taskpy(item_path, config)

                    else:
                        # 锄地任务
                        # self.add_task(item_path, config)
                        pass
        except Exception:
            print(f"请选择任务文件夹,目前没有这个文件夹{state.PATH_TASK}")


    def add_taskpy(self, dir_, config):
        '''
        添加任务
        :return:
        '''

        # 根据导入的文件
        # 获取主窗口的宽度
        width = self.centralWidget().width()
        # 计算每行可以容纳的组件数量
        num_per_row = (width) // (self.column_step * ratio)  # 假设每个组件的宽度为200

        if num_per_row < 1:
            num_per_row = 1
        self.column += 1
        if self.column == num_per_row:
            self.column = 0
            self.row += 1
        # 加载ini配置文件

        f_item = FramePY()

        f_item.setObjectName("frame_cont")
        f_item.raise_()
        f_item.setStyleSheet("""
                                  #frame_cont:hover {
                                      border: 1px solid red;
                                  }
                                  #frame_cont {
                                       background-color: rgba(0, 0, 0, 0);
                                  }
                                  """)
        d = {}
        dir_ = dir_.replace("/", "\\")
        if dir_[-1] == "\\":
            dir_ = dir_[:-1]
        d["type"] = "6"
        try:
            config.set('seting', 'name', dir_.split("\\")[-1].strip())
        except:
            # 添加一个名为 'seting' 的配置节
            config.add_section('seting')
            config.set('seting', 'name', dir_.split("\\")[-1].strip())
        d["name"] = config.get('seting', 'name', fallback=dir_.split("\\")[-1]).strip()
        d["arg"] = config.get('seting', 'arg', fallback=dir_ + "\\参数.ini")
        d["opendir"] = config.get('seting', 'opendir', fallback=dir_)
        d["chuansong"] = config.get('seting', 'chuansong', fallback='')
        d["chuansongmoban"] = config.get('seting', 'chuansongmoban', fallback='')
        d["maodianmoban"] = config.get('seting', 'maodianmoban', fallback='')
        d["cb_is_checked"] = config.get('seting', 'cb_is_checked', fallback='True')
        d["cbb_address"] = config.get('seting', 'cbb_address', fallback='手动录制')
        d["cb_wakuang"] = config.get('seting', 'cb_wakuang', fallback='False')
        d["cb_caiji"] = config.get('seting', 'cb_caiji', fallback='False')
        d["cb_daguai"] = config.get('seting', 'cb_daguai', fallback='True')

        f_item.bt_chuansong.setProperty("OK_chuansong", d["chuansong"] != "")
        f_item.bt_moban.setProperty("OK_chuansongmoban", d["chuansongmoban"] != "")
        f_item.bt_moban_maodian.setProperty("OK_maodianmoban", d["maodianmoban"] != "")
        f_item.bt_chuansong.setObjectName("bt_chuansong")
        f_item.bt_moban.setObjectName("bt_moban")
        f_item.bt_moban_maodian.setObjectName("bt_moban_maodian")
        f_item.led_name.setText(d["name"])

        f_item.bt_arg.setStyleSheet("""
                                       color: rgb(237,182,43);  
                                       border-color: rgb(237,182,43); 
                                                                """)
        f_item.bt_opendir.setStyleSheet("""
                                               color: rgb(237,182,43);  
                                               border-color: rgb(237,182,43); 
                                                                        """)

        f_item.bt_chuansong.setStyleSheet("""
                                                   #bt_chuansong[OK_chuansong="true"] {
                                                   color: rgb(237,182,43);  
                                                   border-color: rgb(237,182,43); 
                                                   }
                                                """)
        f_item.bt_moban.setStyleSheet("""
                                                   #bt_moban[OK_chuansongmoban="true"] {
                                                   color: rgb(237,182,43); 
                                                   border-color: rgb(237,182,43); 
                                                   }
                                                """)
        f_item.bt_moban_maodian.setStyleSheet("""
                                                           #bt_moban_maodian[OK_maodianmoban="true"] {
                                                           color: rgb(237,182,43); 
                                                           border-color: rgb(237,182,43); 
                                                           }
                                                        """)

        if d["cb_is_checked"] == "True":
            f_item.cb_is_checked.setChecked(True)
        else:
            f_item.cb_is_checked.setChecked(False)

        idex = len(self.datas)

        # f_item.led_name.editingFinished.connect(self.save)

        f_item.bt_arg.clicked.connect(lambda: subprocess.Popen(['notepad.exe', d["arg"]]))
        f_item.bt_opendir.clicked.connect(lambda: subprocess.Popen(['explorer', d["opendir"]]))
        f_item.bt_chuansong.clicked.connect(functools.partial(self.clickd_bt_chuansong, idex))

        f_item.bt_moban.clicked.connect(functools.partial(self.clickd_bt_moban, idex))
        f_item.bt_moban_maodian.clicked.connect(functools.partial(self.clickd_bt_moban_maodian, idex))
        f_item.bt_start.clicked.connect(functools.partial(self.clickd_bt_start, idex))
        f_item.bt_del.clicked.connect(functools.partial(self.clickd_bt_del, idex))

        f_item.bt_arg.setVisible(False)
        f_item.bt_opendir.setVisible(False)
        f_item.bt_chuansong.setVisible(False)
        f_item.bt_moban.setVisible(False)
        f_item.bt_moban_maodian.setVisible(False)
        f_item.bt_start.setVisible(False)
        f_item.bt_del.setVisible(False)

        f_item.bt_moban.setToolTip(self.tanChu_img(dir_ + "/" + d["chuansongmoban"]))
        f_item.bt_moban_maodian.setToolTip(self.tanChu_img(dir_ + "/" + d["maodianmoban"]))

        d["f_item"] = f_item

        self.datas.append(d)
        self.g_box.addWidget(f_item, self.row, self.column)

        # 将py模板文件复制一份
        dir_py = os.path.join(d['opendir'], 'main.py')
        dir_ini = os.path.join(d['opendir'], '参数.ini')
        try:
            if not os.path.exists(dir_py):
                shutil.copy('datas/pymode.py', dir_py)
            if not os.path.exists(dir_ini):
                shutil.copy('datas/pymode.ini', dir_ini)
        except:
            pass
    def tanChu_img(self, imgpath):
        '''
        弹出图片
        :param imgpath:
        :return:
        '''
        # ----------------------
        try:
            return f'''<img src='{imgpath}' > '''
        except Exception as err:
            return str(err)


    def clickd_bt_chuansong(self, idex):

        # dl_chuansong = DialogChuansong(self, idex)
        # dl_chuansong.cbb_address.setCurrentText(self.datas[idex]['cbb_address'])
        # dl_chuansong.exec_()
        pass

    def clickd_bt_moban(self, idex):
        temp_path, ok = QFileDialog.getOpenFileName(self, "传送模板图片", "", "图片文件 (*.png)")
        if ok:
            dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)

                # 并且将jiaoben.ini文件也填入数据然后生成
            # 将图片复制到指定目录
            path = dir_ + "/传送模板.png"
            shutil.copy(temp_path, path)
            self.datas[idex]["chuansongmoban"] = "传送模板.png"
            self.datas[idex]["f_item"].bt_moban.setProperty("OK_chuansongmoban", True)
            self.datas[idex]["f_item"].bt_moban.setStyleSheet("""
                                                            #bt_moban[OK_chuansongmoban="true"] {
                                                            color: rgb(237,182,43); 
                                                            border-color: rgb(237,182,43); 
                                                            }
                                                         """)
            self.save_ini(dir_, self.datas[idex])
            self.sg.mysig_tishi.emit(f"导入传送模板成功:{path}")
            print(f"导入传送模板成功:{path}")

    def clickd_bt_moban_maodian(self, idex):
        temp_path, ok = QFileDialog.getOpenFileName(self, "锚点模板图片", "", "图片文件 (*.png)")
        if ok:
            dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)
                # 并且将jiaoben.ini文件也填入数据然后生成
            # 将图片复制到指定目录
            path = dir_ + "/锚点模板.png"
            shutil.copy(temp_path, path)
            self.datas[idex]["maodianmoban"] = "锚点模板.png"
            self.datas[idex]["f_item"].bt_moban_maodian.setProperty("OK_maodianmoban", True)
            self.datas[idex]["f_item"].bt_moban_maodian.setStyleSheet("""
                                                            #bt_moban_maodian[OK_maodianmoban="true"] {
                                                            color: rgb(237,182,43); 
                                                            border-color: rgb(237,182,43); 
                                                            }
                                                         """)
            self.save_ini(dir_, self.datas[idex])
            print(f"导入锚点模板成功:{path}")
            self.sg.mysig_tishi.emit(f"导入锚点模板成功:{path}")

    def clickd_bt_start(self, idex):

        global ysyl
        hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)  # 替换成你实际的窗口句柄
        if hwnd == 0:
            print("错误:请先开游戏!")
            self.sg.mysig_tishi.emit("错误:请先开游戏!")
            return

        left, top, right, bottom = win32gui.GetClientRect(hwnd)
        width = right - left
        height = bottom - top
        if width != 1920 or height != 1080:
            print("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080  建议重新设置分辨率")
            self.sg.mysig_tishi.emit("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080  建议重新设置分辨率")
            return
        self.save()

        def run(idex):

            ret = False
            self.sg.mysig_mouse_through.emit(True)
            state.计次_传送重试次数 = 0
            state.状态_全局暂停 = False
            self.datas[idex]['f_item'].cb_is_checked.setChecked(True)
            for rt in range(self.run_times):
                if self.action_runstartwith.isChecked():
                    print(f"任务包:{state.PATH_TASK} {rt + 1}/{self.run_times}")

                while idex < len(self.datas):
                    if state.状态_检测中:
                        time.sleep(1)
                        continue
                    if self.datas[idex]['f_item'].cb_is_checked.isChecked():
                        ret, arg = self.run(idex)
                        if arg is None:
                            arg = "0"

                        if arg != "0":
                            idex = self.arg_jisuan(arg, idex)

                        if state.状态_循环开关 == False:
                            break
                        if ret == False:
                            if state.状态_需重新传送 == True:
                                state.计次_传送重试次数 += 1
                                if state.计次_传送重试次数 >= 5:
                                    state.计次_传送重试次数 = 0
                                    print("传送重试次数过多,放弃该任务!")
                                else:
                                    print(f"传送重试{state.计次_传送重试次数}")
                                    time.sleep(2)
                                    continue
                            else:
                                break
                    if self.action_runstartwith.isChecked() == False:
                        break
                    state.计次_传送重试次数 = 0
                    idex += 1
                if self.action_runstartwith.isChecked() == False:
                    break
                if state.状态_循环开关 == False:
                    break
                idex = 0

            if state.状态_循环开关 != False:
                if self.action_runstartwith.isChecked():
                    # 切下一个任务包 继续
                    next_tas = self.get_next_task()
                    if state.ON_NEXTPACK == 1 and next_tas != None:
                        print("切下一个任务包!")
                        state.PATH_TASK = next_tas
                        self.sg.mysig_mouse_through.emit(False)
                        self.sg.mysig_next_pack.emit()
                        return
            if ret == True and state.ON_JIXING == 1:
                ysyl.run_jixing()
            if ret == True and state.ON_SHUTDOWN == 1:
                self.sg.mysig_shutdown.emit()
            self.sg.mysig_mouse_through.emit(False)
            state.状态_循环开关 = False

        t = threading.Thread(target=run, args=(idex,))
        if state.python_var > 9:
            t.daemon = True
        else:
            t.setDaemon(True)
        t.start()
    def clickd_bt_del(self, idex):

        dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
        try:
            # 删除整个文件夹
            shutil.rmtree(dir_)
            print(f"文件夹 {dir_} 已被删除")
            self.update_tasks()

        except Exception as e:
            self.update_tasks()
            print(f"删除文件夹时出现错误:{e}")



    def on_timer_timeout(self):
        # 每隔一段时间自动显示图片
        self.sg.mysig_show_xunlu.emit()
        self.sg.mysig_show_yolov.emit()

    # 绑定信号槽
    def connect_set(self):

        self.sg = MySignal()
        state.QT_信号 = self.sg

        self.menu = self.menuBar()
        # 创建一个顶级菜单
        self.menu_file = self.menu.addMenu("文件")

        self.action_addlianzhao = QAction("录制/编辑连招", self)
        self.action_addlianzhao.triggered.connect(self.hotkey_addlianzhao)
        self.menu_file.addAction(self.action_addlianzhao)

        # 创建一个顶级菜单
        self.menu = self.menuBar()
        self.menu_view = self.menu.addMenu("视图")

        # 创建一个动作
        self.action_isShow = QAction("显示检测结果窗口", self)
        self.action_isShow.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F11))
        self.action_isShow.triggered.connect(self.hotkey_isShow)
        self.action_isShow.setCheckable(True)
        self.action_isShow.setChecked(True)
        self.menu_view.addAction(self.action_isShow)

        # 打开预测窗口
        self.hotkey_isShow()


        self.sg.mysig_show_xunlu.connect(self.show_xunlu)
        self.sg.mysig_show_yolov.connect(self.show_yolov)







        # 创建一个顶级菜单
        self.menu_run = self.menu.addMenu("操作")
        # 创建一个动作
        self.action_run_times = QAction(f"设置:当前任务包 执行次数:{self.run_times}", self)
        self.action_run_times.triggered.connect(self.hotkey_run_times)
        self.menu_run.addAction(self.action_run_times)






    def hotkey_addlianzhao(self):

        # 弹出一个选择弹窗 选项为  录制还是文件中选择
        msgBox = QMessageBox(self)
        msgBox.setWindowTitle("配置连招")  # 设置消息框标题
        msgBox.setIcon(QMessageBox.Information)

        msgBox.setText("当前连招:" + state.LIANZHAO + "\n请选择一个配置方式:")
        msgBox.addButton("录制", QMessageBox.AcceptRole)
        msgBox.addButton("回放", QMessageBox.ApplyRole)
        msgBox.addButton("文件中选择", QMessageBox.RejectRole)
        result = msgBox.exec_()
        if result == QMessageBox.AcceptRole:
            try:
                text, ok = QInputDialog.getText(self, '提示', '请输入脚本名:', text=state.LIANZHAO)
                if not ok:
                    return
                if text == "":
                    return
                elif text[-4:] != ".txt":
                    state.LIANZHAO = text + ".txt"
                else:
                    state.LIANZHAO = text
                print("可以开始录制了,按F8开始/停止")
                self.sg.mysig_tishi.emit("可以开始录制了,按F8开始/停止")
                state.状态_是否禁止录制 = False
                state.状态_是否开始录制 = False


                state.录制_脚本文本 = ""
                tangbaowss.send_msg("是否禁止录制#@@#假")
                tangbaowss.send_msg("是否开始录制#@@#假")
                tangbaowss.send_msg(f"全局脚本名#@@#{state.LIANZHAO.replace('.txt', '')}")
                # hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)  # 替换成你实际的窗口句柄
                hwnd = 25036058
                tangbaowss.send_msg(f"全局hwnd#@@#{hwnd}")
                self.timer_luzhi_lianzhao.start(200)
            except:
                pass
        elif result == QMessageBox.RejectRole:
            if state.状态_是否回放中 == True:
                return
            if not os.path.exists(state.PATH_JIAOBEN):
                os.makedirs(state.PATH_JIAOBEN)
            state.状态_是否回放中 = True
            state.状态_循环开关 = True
            jiaoben = os.path.abspath(os.path.join(state.PATH_JIAOBEN, state.LIANZHAO))
            message = f"解析脚本#@@#{jiaoben}"
            tangbaowss.send_msg(message)
            hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)  # 替换成你实际的窗口句柄
            set_window_activate(hwnd)
            tangbaowss.send_msg("脚本执行#@@#1")
            self.timer_huifang.start(200)
        elif result == QMessageBox.Warning:
            self.hotkey_setlianzhao()
    def hotkey_setlianzhao(self):
        # 弹出一个有分组框的窗口
        temp_path, ok = QFileDialog.getOpenFileName(self, "连招脚本", state.PATH_JIAOBEN, "脚本文件 (*.txt)")
        if ok:
            _, state.LIANZHAO = os.path.split(temp_path)
            dir_ = os.path.join(state.PATH_JIAOBEN, state.LIANZHAO)
            try:
                shutil.copy(temp_path, dir_)
            except:
                pass
            print("选择连招脚本成功!")
            self.save_ini_seting()

    def hotkey_run_times(self):
        try:
            text, ok = QInputDialog.getText(self, '执行次数', f'当前任务包:{state.PATH_TASK}\n请输入执行次数:',
                                            text=str(self.run_times))
            if not ok:
                return
            if text == "":
                return

            if int(text) < 1:
                self.run_times = 1
            else:

                self.run_times = int(text)

            self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
            print(f"设置:当前任务包 执行次数:{self.run_times}")

            # 创建 ConfigParser 对象
            config_main = configparser.ConfigParser()
            # 添加节和键-值对
            config_main['seting'] = {
                "run_times": str(self.run_times),
            }
            # 写入配置到 INI 文件
            with open(os.path.join(state.PATH_TASK, "细节参数.ini"), 'w') as configfile:
                config_main.write(configfile)

        except:
            pass

    def show_xunlu(self, image_path="./datas/111.png"):
        # 加载本地图片
        pixmap = QPixmap(image_path)

        # 设置图片到 lb_xunlu 控件
        self.fromshow.lb_xunlu.setPixmap(pixmap)

        # 设置宽度固定,高度自适应
        self.fromshow.window_width = int(self.fromshow.window_height * pixmap.width() / pixmap.height())
        self.fromshow.lb_xunlu.setFixedHeight(self.fromshow.window_height)
        self.fromshow.lb_xunlu.setFixedWidth(self.fromshow.window_width)
        self.fromshow.lb_xunlu.setScaledContents(True)

    def show_yolov(self, image_path="./datas/111.png"):
        # 加载本地图片
        pixmap1 = QPixmap(image_path)

        # 设置图片到 lb_yolov 控件
        self.fromshow.lb_yolov.setPixmap(pixmap1)

        # 根据窗口宽度调整 lb_yolov 的位置
        if self.fromshow.width() >= self.fromshow.lb_yolov.width() + self.fromshow.lb_xunlu.width():
            self.fromshow.lb_yolov.move(self.fromshow.lb_xunlu.width(), 0)
        else:
            self.fromshow.lb_yolov.move(0, self.fromshow.lb_xunlu.height())

    # 打开预测窗口
    def hotkey_isShow(self):
        # 尝试创建悬浮窗口实例
        if not hasattr(self, "fromshow"):
            self.fromshow = FormShow(self)  # 创建悬浮窗口实例

            self.addDockWidget(Qt.TopDockWidgetArea, self.fromshow)  # 添加悬浮窗口到主窗口

    # 更新布局
    def update_layout(self):
        try:
            # 获取主窗口的宽度
            width = self.centralWidget().width()

            # 计算每行可以容纳的组件数量
            num_per_row = (width) // (self.column_step * ratio)
            if num_per_row < 1:
                num_per_row = 1

            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)

            # 重新添加组件到网格布局
            for i, data in enumerate(self.datas):
                self.row = int(i // num_per_row)
                self.column = int(i % num_per_row)
                self.g_box.addWidget(data["f_item"], self.row, self.column)
        except:
            pass

    # 设置主窗口样式
    def set_ui(self):
        # 设置主题样式为 Flatwhite
        from qt_material import apply_stylesheet
        apply_stylesheet(app, theme='dark_pink.xml')

        # 设置窗口样式表
        self.setStyleSheet("""
        QScrollBar::handle:horizontal {
            background-color: #A50F2C;  /* 设置滑块颜色 */
        }
        QScrollBar::handle:horizontal:hover {
            background-color: #FF1744;  /* 设置滑块颜色 */
        }
        QPushButton:hover {
            background-color: #DFC472;  /* 设置颜色 */
        }
        QPlainTextEdit {
            padding: 0px;
            margin: 0px;
        }
        QPushButton {
            padding: 0px;
            margin: 1px;
        }
        """ + "font-family: {}; font-size: {}pt;".format(font.family(), font.pointSize()))


# 主程序入口
if __name__ == '__main__':
    # 获取屏幕宽度和高度
    screen_width = ctypes.windll.user32.GetSystemMetrics(0)
    screen_height = ctypes.windll.user32.GetSystemMetrics(1)

    # 初始化QT应用
    app = QApplication(sys.argv)

    # 计算分辨率比例
    ratio = screen_width / 2560

    # 设置全局字体大小
    base_font_size = 13
    new_font_size = int(base_font_size * ratio)
    font = QFont("Arial", new_font_size)

    item_width = 320
    item_height = 240
    item_height_min = 60

    # 创建主窗口实例
    window_main = MainWindow()

    # 显示主窗口
    window_main.show()

    # 监听消息不关闭
    sys.exit(app.exec_())

9、测试

我们打开一个新文件夹,然后获取到句柄后进行修改,我上面这个全量代码

hotkey_addlianzhao函数中 强制写死了hwn窗口句柄id,hwnd = 25036058  注意修改

下面演示我们先点文件--添加录制,按一下F8,然后在所需窗口内输入自己想要的操作,然后在按一次F8即可保存文件操作记录文件

 

 

现在我们已经能拿到录制的脚本操作了,我们要给qt上进行添加

10、添加子任务

test.txt直接用我们上面拿到的录制 文件

vi jiaoben.ini

[seting]
type = 3
name = 滑翔
cb_auto_f = False
jiaoben = test.txt
chuansong = 
cbb_address = 手动录制
chuansongmoban = 
maodianmoban = 
cb_is_checked = True

update_tasks 函数会根据我们脚本中的type =3 来判断添加什么类型的任务

                    elif config.get('seting', 'type', fallback='1') == "3":
                        # 脚本任务
                        self.add_taskjiaoben(item_path, config)

                        #这里原本是被注释的

11、添加任务脚本

    def add_taskjiaoben(self, dir_, config):
        '''
        添加脚本任务
        :return:
        '''
        # 根据导入的文件
        # 获取主窗口的宽度
        width = self.centralWidget().width()
        # 计算每行可以容纳的组件数量
        num_per_row = (width) // (self.column_step * ratio)  # 假设每个组件的宽度为200

        if num_per_row < 1:
            num_per_row = 1
        self.column += 1
        if self.column == num_per_row:
            self.column = 0
            self.row += 1
        # 加载ini配置文件
        f_item = FrameItemJiaoBen()
        f_item.setObjectName("frame_cont")
        f_item.raise_()
        f_item.setStyleSheet("""
                               #frame_cont:hover {
                                   border: 1px solid red;
                               }
                               #frame_cont {
                                    background-color: rgba(0, 0, 0, 0);
                               }
                               """)
        d = {}
        d["type"] = "3"
        dir_ = dir_.replace("/", "\\")
        if dir_[-1] == "\\":
            dir_ = dir_[:-1]
        try:
            config.set('seting', 'name', dir_.split("\\")[-1].strip())
        except:
            # 添加一个名为 'seting' 的配置节
            config.add_section('seting')
            config.set('seting', 'name', dir_.split("\\")[-1].strip())
        d["name"] = config.get('seting', 'name', fallback=dir_.split("\\")[-1]).strip()
        d["cb_auto_f"] = config.get('seting', 'cb_auto_f', fallback='True')
        d["chuansong"] = config.get('seting', 'chuansong', fallback='')
        d["cbb_address"] = config.get('seting', 'cbb_address', fallback='手动录制')
        d["jiaoben"] = config.get('seting', 'jiaoben', fallback='')
        d["chuansongmoban"] = config.get('seting', 'chuansongmoban', fallback='')
        d["maodianmoban"] = config.get('seting', 'maodianmoban', fallback='')
        d["cb_is_checked"] = config.get('seting', 'cb_is_checked', fallback='True')
        f_item.bt_chuansong.setProperty("OK_chuansong", d["chuansong"] != "")
        f_item.bt_jiaoben.setProperty("OK_jiaoben", d["jiaoben"] != "")
        f_item.bt_moban.setProperty("OK_chuansongmoban", d["chuansongmoban"] != "")
        f_item.bt_moban_maodian.setProperty("OK_maodianmoban", d["maodianmoban"] != "")
        f_item.bt_chuansong.setObjectName("bt_chuansong")
        f_item.bt_moban.setObjectName("bt_moban")
        f_item.bt_moban_maodian.setObjectName("bt_moban_maodian")
        f_item.led_name.setText(d["name"])

        f_item.bt_jiaoben.setStyleSheet("""
                                                       #bt_jiaoben[OK_jiaoben="true"] {
                                                       color: rgb(237,182,43);  
                                                       border-color: rgb(237,182,43); 
                                                       }
                                                    """)

        f_item.bt_chuansong.setStyleSheet("""
                                                #bt_chuansong[OK_chuansong="true"] {
                                                color: rgb(237,182,43);  
                                                border-color: rgb(237,182,43); 
                                                }
                                             """)
        f_item.bt_moban.setStyleSheet("""
                                                #bt_moban[OK_chuansongmoban="true"] {
                                                color: rgb(237,182,43); 
                                                border-color: rgb(237,182,43); 
                                                }
                                             """)
        f_item.bt_moban_maodian.setStyleSheet("""
                                                        #bt_moban_maodian[OK_maodianmoban="true"] {
                                                        color: rgb(237,182,43); 
                                                        border-color: rgb(237,182,43); 
                                                        }
                                                     """)

        if d["cb_is_checked"] == "True":
            f_item.cb_is_checked.setChecked(True)
        else:
            f_item.cb_is_checked.setChecked(False)
        if d["cb_auto_f"] == "True":
            f_item.cb_auto_f.setChecked(True)
        else:
            f_item.cb_auto_f.setChecked(False)

        idex = len(self.datas)

        f_item.bt_jiaoben.clicked.connect(functools.partial(self.clickd_bt_jiaoben, idex))
        f_item.bt_chuansong.clicked.connect(functools.partial(self.clickd_bt_chuansong, idex))
        f_item.bt_moban.clicked.connect(functools.partial(self.clickd_bt_moban, idex))
        f_item.bt_moban_maodian.clicked.connect(functools.partial(self.clickd_bt_moban_maodian, idex))
        f_item.bt_start.clicked.connect(functools.partial(self.clickd_bt_start, idex))
        f_item.bt_del.clicked.connect(functools.partial(self.clickd_bt_del, idex))
        d["f_item"] = f_item

        f_item.bt_start.setVisible(False)
        f_item.bt_jiaoben.setVisible(False)
        f_item.bt_chuansong.setVisible(False)
        f_item.bt_moban_maodian.setVisible(False)
        f_item.bt_moban.setVisible(False)
        f_item.bt_start.setVisible(False)
        f_item.bt_del.setVisible(False)
        f_item.cb_auto_f.setVisible(False)

        f_item.bt_moban.setToolTip(self.tanChu_img(dir_ + "/" + d["chuansongmoban"]))
        f_item.bt_moban_maodian.setToolTip(self.tanChu_img(dir_ + "/" + d["maodianmoban"]))

        self.datas.append(d)
        self.g_box.addWidget(f_item, self.row, self.column)

12、添加ui配置

class FrameItemJiaoBen(QFrame, Ui_formitemjiaoben):
    def __init__(self, parent=None):
        super(QFrame, self).__init__(parent)
        self.setupUi(self)
        self.set_ui()
        self.is_zd = False
        w = int(item_width * ratio)
        h = int(item_height_min * ratio)
        self.setMinimumSize(w + int(1 * ratio), h)
        self.setMaximumSize(w, h)
        # self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint | Qt.WindowStaysOnTopHint)

    def set_ui(self):
        pass

    def mousePressEvent(self, event):
        if not self.is_zd:
            self.is_zd = not self.is_zd
            # 在这里添加鼠标进入时的逻辑
            w = int(item_width * ratio)
            h = int(item_height * ratio)

            self.setMinimumSize(w + int(1 * ratio), h)
            self.setMaximumSize(w, h)
            self.bt_start.setVisible(True)
            self.bt_jiaoben.setVisible(True)
            self.bt_chuansong.setVisible(True)
            self.bt_moban_maodian.setVisible(True)
            self.bt_moban.setVisible(True)
            self.bt_start.setVisible(True)
            self.bt_del.setVisible(True)
            self.cb_auto_f.setVisible(True)
        else:
            self.is_zd = not self.is_zd
            w = int(item_width * ratio)
            h = int(item_height_min * ratio)
            self.setMinimumSize(w + int(1 * ratio), h)
            self.setMaximumSize(w, h)
            self.bt_start.setVisible(False)
            self.bt_jiaoben.setVisible(False)
            self.bt_chuansong.setVisible(False)
            self.bt_moban_maodian.setVisible(False)
            self.bt_moban.setVisible(False)
            self.bt_start.setVisible(False)
            self.bt_del.setVisible(False)
            self.cb_auto_f.setVisible(False)

13、添加ui xml

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>formitemjiaoben</class>
 <widget class="QWidget" name="formitemjiaoben">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>229</width>
    <height>155</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QGridLayout" name="gridLayout">
   <item row="0" column="0" colspan="4">
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QLineEdit" name="led_name">
       <property name="minimumSize">
        <size>
         <width>0</width>
         <height>0</height>
        </size>
       </property>
       <property name="maximumSize">
        <size>
         <width>999</width>
         <height>9999</height>
        </size>
       </property>
       <property name="styleSheet">
        <string notr="true">color: rgb(255, 255, 255);</string>
       </property>
       <property name="text">
        <string>蒙德锄地</string>
       </property>
       <property name="alignment">
        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QCheckBox" name="cb_is_checked">
       <property name="minimumSize">
        <size>
         <width>0</width>
         <height>0</height>
        </size>
       </property>
       <property name="maximumSize">
        <size>
         <width>999999</width>
         <height>9999999</height>
        </size>
       </property>
       <property name="layoutDirection">
        <enum>Qt::RightToLeft</enum>
       </property>
       <property name="styleSheet">
        <string notr="true"/>
       </property>
       <property name="text">
        <string/>
       </property>
       <property name="checked">
        <bool>true</bool>
       </property>
      </widget>
     </item>
    </layout>
   </item>
   <item row="1" column="0" colspan="2">
    <widget class="QCheckBox" name="cb_auto_f">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="font">
      <font>
       <family>ADMUI3Lg</family>
       <pointsize>14</pointsize>
      </font>
     </property>
     <property name="focusPolicy">
      <enum>Qt::TabFocus</enum>
     </property>
     <property name="text">
      <string>自动按F</string>
     </property>
     <property name="checked">
      <bool>true</bool>
     </property>
    </widget>
   </item>
   <item row="1" column="2">
    <widget class="QPushButton" name="bt_jiaoben">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>999999</width>
       <height>999999</height>
      </size>
     </property>
     <property name="text">
      <string>脚本</string>
     </property>
    </widget>
   </item>
   <item row="1" column="3">
    <widget class="QPushButton" name="bt_start">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>999999</width>
       <height>999999</height>
      </size>
     </property>
     <property name="text">
      <string>启动</string>
     </property>
    </widget>
   </item>
   <item row="2" column="0">
    <widget class="QPushButton" name="bt_chuansong">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>999999</width>
       <height>999999</height>
      </size>
     </property>
     <property name="text">
      <string>传送
脚本</string>
     </property>
    </widget>
   </item>
   <item row="2" column="1">
    <widget class="QPushButton" name="bt_moban">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>999999</width>
       <height>999999</height>
      </size>
     </property>
     <property name="text">
      <string>传送
模板</string>
     </property>
    </widget>
   </item>
   <item row="2" column="2">
    <widget class="QPushButton" name="bt_moban_maodian">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>999999</width>
       <height>999999</height>
      </size>
     </property>
     <property name="text">
      <string>锚点
模板</string>
     </property>
    </widget>
   </item>
   <item row="2" column="3">
    <widget class="QPushButton" name="bt_del">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>999999</width>
       <height>999999</height>
      </size>
     </property>
     <property name="text">
      <string>删除
任务</string>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

记得转py文件

 13、

;