玩亚索很费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键连点的次数与频率的不确定性:
对这个代码的两次实验,都是只按了一下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):
效果不错,而且不会出现松开q键仍然会继续连点的情况。
ps.欢迎拍砖,对多线程不太熟悉,如果有更好的解决方案请留言。