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.youkuaiyun.com/download/qq_42883074/89820151

源码
https://download.youkuaiyun.com/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:
                    # 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("########回车打开录制开关,然后按下F8开始录制,你觉得录制好了在按下F8结束\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、添加监听

我们当按下home的时候触发录制,按下end时关闭录制

# -*- coding: gbk -*-
import time
from pynput import keyboard
import state  #
from logger_module import logger
from wss import 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  # 设置停止标志

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



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

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



    #按下end录制关闭
    if key == keyboard.Key.end:
        # print(key)
        if state.状态_是否开始录制 == True:
            logger.info("录制关闭")
            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、键鼠回放

按下F6回放,注意调用的时候的句柄id还有本地回放的文件路径

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  # 设置停止标志

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

    # 按下end录制关闭
    if key == keyboard.Key.end:
        print(key)
        if state.状态_是否开始录制 == True:
            logger.info("录制关闭")
            send_msg("是否禁止录制#@@#真")  # 禁止录制
            send_msg("是否开始录制#@@#假")  # 关闭的录制开关

    # 按下F6回放
    if key == keyboard.Key.f6:
        print("开始回放")
        hwnd = 38601640
        set_window_activate(hwnd)
        config = configparser.ConfigParser()
        # 加载 INI 文件

        state.状态_是否回放中 = True
        message = f"解析脚本#@@#C:/Users/Administrator/PycharmProjects/yolo8test/demo/1111.txt"
        send_msg(message)


        send_msg("脚本执行#@@#1")

    #按下F7暂停回放
    if key == keyboard.Key.f7:
        send_msg("是否暂停#@@#真")
        print("暂停回放")

    #取消暂停回放
    if key == keyboard.Key.f8:
        send_msg("是否暂停#@@#假")
        print("取消暂停回放")

15、服务端全量

# -*- 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:
                    # 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("回车退出")

16、客户端全量

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

import win32con
import win32gui
from pynput import keyboard
import threading
import state  #
from logger_module import logger
from wss import send_msg

# 初始化标志变量
stop_listener = False


def set_window_activate(hwnd):
    """强制激活窗口"""
    try:
        # 判断窗口是否为最小化
        if win32gui.IsIconic(hwnd):
            # 还原窗口
            win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)

        # 将窗口置顶
        win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
        # 取消置顶
        win32gui.SetWindowPos(hwnd, win32con.HWND_NOTOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)

        win32gui.SetForegroundWindow(hwnd)
    except:
        pass


# 检查按键
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  # 设置停止标志

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

    # 按下end录制关闭
    if key == keyboard.Key.end:
        print(key)
        if state.状态_是否开始录制 == True:
            logger.info("录制关闭")
            send_msg("是否禁止录制#@@#真")  # 禁止录制
            send_msg("是否开始录制#@@#假")  # 关闭的录制开关

    # 按下F6回放
    if key == keyboard.Key.f6:
        print("开始回放")
        hwnd = 63046196
        set_window_activate(hwnd)
        config = configparser.ConfigParser()
        # 加载 INI 文件d

        state.状态_是否回放中 = True
        message = f"解析脚本#@@#C:/Users/Administrator/PycharmProjects/yolo8test/demo/1111.txt"
        send_msg(message)


        send_msg("脚本执行#@@#1")



    if key == keyboard.Key.f7:
        send_msg("是否暂停#@@#真")
        print("暂停回放")

    if key == keyboard.Key.f8:
        send_msg("是否暂停#@@#假")
        print("取消暂停回放")






# 启动按键监听器
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()

17、录制、回放、暂停、取消暂停、验证

先获取下窗口句柄,运行后把鼠标放到窗口上然后等待窗口句柄显示

import time
import win32api
import win32gui
 
 
time.sleep(2)
 
point = win32api.GetCursorPos()  #win32api.GetCursorPos 获取鼠标当前的坐标(x,y)
 
hwnd = win32gui.WindowFromPoint(point)  #查看坐标位置窗口的句柄
 
print(hwnd)  #输出句柄

效果图

注意上面代码中回放按键改成F6了,下图没换

最好别录制中文输入,有时候会有奇奇怪怪的影响,尽量单独字母按键即可

(暂定忘操作了,不过是可用的)

二、UI调用

现在开始我们又需要回到main.py中的主窗口继续编写

1、添加顶层菜单

    def connect_set(self):

        ......



        # 创建一个顶级菜单
        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.action_addtaskjiaoben = QAction("新增-脚本回放任务", self)
        self.action_addtaskjiaoben.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_3))
        self.action_addtaskjiaoben.triggered.connect(self.hotkey_addtaskjiaoben)
        self.menu_file.addAction(self.action_addtaskjiaoben)

2、添加导入录制脚本函数

我们这个ui的项目模拟,是新开的目录,我们前面已经整好的函数需要在这里调用,我们将之前项目中写的lanrenauto目录 整个拷贝到我们现在这个测试项目下面,留给下面备用

from lanrenauto.moni.moni import *


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

       ...

        #添加监听器,把我们前面写的那个客户端在这里加上
        #这样在初始化的时候就会有一个循环监听我们操作的工具
        from wsstest import start_listener_thread
        listener_thread = start_listener_thread()





    #添加录制导入脚本
        # 添加录制导入脚本
    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

                logger.info("可以开始录制了")
                
                #修改ui页面提示显示
                self.sg.mysig_tishi.emit("可以开始录制了,按home开始/end停止")

                # send_msg(f"全局脚本名#@@#{state.LIANZHAO.replace('.txt', '')}")
                # hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)  # 替换成你实际的窗口句柄
                # send_msg(f"全局hwnd#@@#{hwnd}")

                self.timer_luzhi_lianzhao.start(200)

            except:
                pass
        elif result == QMessageBox.RejectRole:
            pass
        elif result == QMessageBox.Warning:
            pass

因为我们前面自己做了一个监听按键的工具,工具里面包含了按键后的录制回放暂停的操作,所以这里会和原项目不太一样,原项目是按照c++的工具,默认按下F8录制,再次按下F8停止的,我们这里改一下,就是当开始录制的时候在ui页面给个提示信息(self.sg.mysig_tishi.emit("可以开始录制了,按home开始/end停止")  

然后把ui页面的信息写道state的变量里,当页面提示可用录制后,我们正常按键录制就行,结束时再次修改提示变量即可,回放同理,下面可能多少和原项目不太一样了

3、修改服务端导出文件

这里将之前写死的1111.txt改成读取state.LIANZHAO 变量的路径,因为前面会在ui录制的时候名称,这里会同步变更为新的文件名

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:
                    # 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.录制_脚本文本)
                    print(state.PATH_JIAOBEN + state.LIANZHAO)
                    with open(state.PATH_JIAOBEN + state.LIANZHAO, "w", encoding="gbk", newline='') as f:
                        f.write(state.录制_脚本文本)

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

完事

再往下其实没什么可看的了,前面这段关于录制和回放的逻辑我按照自己习惯做了修改,可以去看看原项目是怎么写的,下面就是整了个添加任务的ui模板,然后在原项目中多一个手动添加任务到主页面,和我们之前直接在TASK下面定义目录和任务一样,就是可以临时建了

各种按键上面墨迹的太久了,还不如去多看看寻路和yolo训练的部分ε=ε=ε=(~ ̄▽ ̄)~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值