利用python实现键盘连点器

玩亚索很费q键和r键,所以想做一个键盘连点器。

功能要求

1.按下按键,开始连击,松开按键,连击停止;
2.只对q键和r键有效;
3.具备一定的稳定性,且按键松开后能够立即停止连击,不能拖泥带水;

依赖的库

1.键盘监听库:pynput(pyhook也可以监听键盘,但是安装麻烦)
2.模拟键盘输入的库:pywin32(pynput也可以模拟键盘输入,但是不够底层,不能用于游戏)
3.ctypes,用于获取模拟键盘事件时需要的扫描码参数
关于pywin32和ctypes进行键盘操作的一些用法可以在 这篇公众号文章.看到,在此感谢 孤独的S 这位老铁。

代码实现

  • 连点器需要两个函数来实现,一个是控制函数,一个是连点函数,两者并行,所以需要用到多线程
  • 控制函数和连点函数之间需要进行信息沟通,一开始的方案是利用全局变量,参考的是 这篇文章.
    一、利用全局参数的方式实现
from pynput.keyboard import Key, Listener
import time
import win32api
import win32con
import ctypes
import os
import threading


#定义全局变量q作为连点的开关参数
q = 0

# 监听按下
def on_pressq(key):
    global q
    #判断按下的键是不是q键
    if str(key)=="'"+'q'+"'" and q == 0:
        #如果是q键被按下,将全局变量q设为1
        q=1
        print('监听到按下q')
        
#监听释放
def on_releaseq(key):
    global q
    if str(key)=="'"+'q'+"'":
    	#如果q键被释放,全局变量q设为0
        q=0
        print('监听到释放q')
#定义连击函数
def press_q():
    global q
    #这个连点函数是要一直循环运行的
    while True:
    	#如果全局变量q为1,那么开始连点
        if q == 1:
        	#定义好获取相应按键的扫描参数的函数	
            MapVirtualKey = ctypes.windll.user32.MapVirtualKeyA   
            #连击5次q键,0x51是q键的虚拟码,后面的MapVirtualKey(0x51, 0)可以得到q键的扫描码
            #如果想在游戏中起作用,必须同时输入虚拟码和扫描码
            for i in range(5):
                win32api.keybd_event(0x51, MapVirtualKey(0x51, 0), 0, 0)
                time.sleep(0.01)
                win32api.keybd_event(0x51, MapVirtualKey(0x51, 0), win32con.KEYEVENTF_KEYUP, 0)
            print('自动按q')


# 运行线程
#pynput源码中解释说Listener是一个继承自threading.Thread的类,所以在定义线程的时候采用这种语法
t1 = Listener(on_press=on_pressq, on_release=on_releaseq)
t2 = threading.Thread(target=press_q, name='sendThreadq')
t1.start()
t2.start()

os.system("pause")

这个方法确实能用,然而存在一些问题,例如在连点过程中,监控q键按下与抬起的线程t1也一直在运行,这就导致全局变量q被频繁的反复赋值为1和0,q被赋值又会反过来影响连点线程,且多线程的执行顺序是不确定的,这就导致q键连点的次数与频率的不确定性:
利用python实现键盘连点器_第1张图片
利用python实现键盘连点器_第2张图片
对这个代码的两次实验,都是只按了一下q键,然而分别进行了11次和16次连点,连点速度不够稳定,且在输出栏的监测数据中可以看出,在连点的过程中q键的动作不断的被t1线程监测到,这也意味着全局变量q不断地被重新赋值,这加剧了代码的不稳定性,有时会出现松开q键,代码继续自动连点了十多次的情况。
二、使用信号量进行线程同步
这里参考了这篇文章. 利用信号量来稳定连点的次数,并辅以全局变量来控制监控线程的介入,避免两个进程之间的交叉影响。

from pynput.keyboard import Key, Listener
import time
import win32api
import win32con
import ctypes
import os
import threading

#分别定义q和r键的信号量对象
semaphoreq = threading.Semaphore(0)
semaphorer = threading.Semaphore(0)

#定义全局变量作为监测线程介入的开关
q = 0
r = 4

def on_pressq(key):
    # 监听按键q
    global q
    if str(key)=="'"+'q'+"'" and q == 0:
        print('按下q',q)
        #q信号量加一
        semaphoreq.release()

def on_pressr(key):
    # 监听按键r
    global r
    if str(key)=="'"+'r'+"'" and r == 4:
         print('按下r',r)
         #r信号量加一
         semaphorer.release()

def press_q():
    global q
    while True:
    	#消费一个q信号量
        semaphoreq.acquire()
        #全局变量q赋值为1,阻断监控函数的介入
        q = 1
        MapVirtualKey = ctypes.windll.user32.MapVirtualKeyA
        #time.sleep(0.1)
        for i in range(5):
            win32api.keybd_event(0x51, MapVirtualKey(0x51, 0), 0, 0)
            time.sleep(0.05)
            win32api.keybd_event(0x51, MapVirtualKey(0x51, 0), win32con.KEYEVENTF_KEYUP, 0)
        #全局变量q赋值为0,监控函数又可以介入了
        q = 0
        print('自动按q')

def press_r():
    global r
    while True:
    	#消费一个r信号量
        semaphorer.acquire()
        r = 5
        MapVirtualKey = ctypes.windll.user32.MapVirtualKeyA
        # time.sleep(0.1)
        for i in range(5):
            win32api.keybd_event(0x52, MapVirtualKey(0x52, 0), 0, 0)
            time.sleep(0.05)
            win32api.keybd_event(0x52, MapVirtualKey(0x52, 0), win32con.KEYEVENTF_KEYUP, 0)
        r = 4
        print('自动按r')

# 运行进程
t1 = Listener(on_press=on_pressq)
t2 = Listener(on_press=on_pressr)
t3 = threading.Thread(target=press_q, name='sendThreadq')
t5 = threading.Thread(target=press_r, name='sendThreadr')
t1.start()
t2.start()
t3.start()
t5.start()


os.system("pause")

通过信号量来稳定连点次数,通过全局变量来控制监控函数(信号量的生产者)的介入(虽然仍然有监测行为,但是却没有赋值以及信号量生产行为),减少了控制函数和连点函数之间的交叉干扰,删掉了没有必要的按键抬起监测函数。再来实验一下(只按一下q):
利用python实现键盘连点器_第3张图片
利用python实现键盘连点器_第4张图片
效果不错,而且不会出现松开q键仍然会继续连点的情况。

ps.欢迎拍砖,对多线程不太熟悉,如果有更好的解决方案请留言。

你可能感兴趣的:(小工具)