PYTHON多线程实现全局鼠标/键盘监控

发布于 2023-01-20  588 次阅读


准备工作

  • 上篇写了一些简单的基于鼠标和按键模拟的py实现,但是在实际应用环境中,可能遇到此类程序无法处理的情况,因为上期的程序的命令是没有触发条件的,整个控制链都是基于提前写入的任务,无法实现触发式的调用.本篇将讨论python对鼠标和键盘的全局监控,并根据监控信息触发控制链
  • 事情起始于我的一个神奇的朋友(为什么说他神奇呢🤣🤣🤣保密!),他用模拟器玩安卓游戏,但是他使用的模拟器对资源利用率低,就换成了hyper-v,但是hyper-v不支持mumu模拟点击,就只能自己写一个了
  • 根据使用的情况,程序需要实现按下某个指定的按键,模拟鼠标点击指定坐标的位置,同时因为是游戏环境,所以要求模拟点击的时间需要尽量的快,全局监控(因为屏幕要显示游戏,py程序肯定不能在前端),并且模拟点击不能呈现鼠标被强行移动的效果
钩子函数是Windows消息处理机制的一部分,通过设置“钩子”,应用程序可以在系统级对所有消息、事件进行过滤,访问在
正常情况下无法访问的消息。钩子的本质是一段用以处理系统消息的程序,通过系统调用,把它挂入系统。
  • 钩子函数也是windows api函数,各种语言都可以调用,比如本文在做键盘全局监控时使用python的keyboard库,调一个函数就搞定了,别人封装好了的,整个过程比vb里面简单多了

刚才是水字数,现在才是准备工作

  • 不管是上次的模拟,还是这次的全局监控,都需要知道鼠标坐标,比如模拟点击某个按钮,就需要知道这个按钮在电脑屏幕显示的坐标,但是如果一点一点的试有太慢了,那就先写个实时显示坐标的程序吧
  • win32api.GetCursorPos()函数可以返回鼠标的实时坐标(包括x和y),将值传给数组pos
pos=win32api.GetCursorPos()
  • 如果这么写,后面单独对x和y的使用就是pos[0]和pos[1],也可以写成下面这样
x,y=win32api.GetCursorPos()
  • 再加个循环,设置一下间隔时间,不然一秒输十个数据啥都看不清
import win32gui
import win32con
import win32api
import pyautogui 
import time

while 1:
    pos = win32api.GetCursorPos()
    print(int(pos[0]), int(pos[1]))
    time.sleep(1)

img

  • 刚才是循环输出鼠标坐标,在有些场景下(比如录制鼠标操作),不能循环记录而是触发式记录,下面再贴一个我写的点击鼠标左键记录鼠标位置的代码,有关参数自行百度
from re import X
import win32gui
import win32con
import win32api
import pyautogui 
import time

x=0
y=0
while True:
    a = win32api.GetKeyState(0x01)
    if a<0:
        pos = win32api.GetCursorPos()
        if pos[0]!=x and pos[1]!=y:
            print(int(pos[0]), int(pos[1]))
            x=pos[0]
            y=pos[1]

编写开始(全局键盘监控)

  • 为了整洁,我们把上面的代码放入函数方便调用
def cursor_point():
    pos = win32api.GetCursorPos()
    return int(pos[0]), int(pos[1])
  • keyboard.wait()函数接收一个按键参数,当键盘输入该按键时程序才能继续执行(这种监视是全局监视,即使python脚本不在前端仍然能执行)
如:
keyboard.wait('2')
print("miaomiao")

脚本后台执行,输入2,终端输出miaomiao
  • 这里要求"鼠标模拟点击坐标,而不是鼠标被强制移动过去点击",这个实际上很好实现,只有把鼠标移动的时间缩短到很小,同时鼠标模拟点击完成后在很短的时间内自动移动会原本的位置,就可以实现"模拟点击坐标",因为肉眼无法发现鼠标的移动
#把获取坐标的函数写好后面要用
def cursor_point():
    pos = win32api.GetCursorPos()
    return int(pos[0]), int(pos[1])

#设置键盘监视,输入1就执行下去
keyboard.wait('1')

#获取鼠标的坐标,方便一会儿模拟点击完了之后移动回来
me=cursor_point()

#将鼠标快速移动到欲点击位置,实施模拟点击,再将鼠标快速移动会初始位置
pyautogui.moveTo(500, 400,duration = 0.001)
pyautogui.click(clicks = 1, button ='left', interval = 0.0005)
pyautogui.moveTo(me[0], me[1],duration = 0.01)
  • 然而这里有一个坑,pyautogui.PAUSE设定了执行任何一个pywin32函数后被强制要求暂停的时间,如果不设置的话这个值默认是0.1的,所以即使duration = 0.001属性设置为0.001,鼠标仍然会在点击位置停顿0.1秒再继续执行导致肉眼可见的移动,所以加入pause值的设置,下面是假的完整代码
import win32gui
import win32con
import win32api
import pyautogui 
import keyboard 
import threading

pyautogui.PAUSE=0.01

def cursor_point():
    pos = win32api.GetCursorPos()
    return int(pos[0]), int(pos[1])

while 1:
    keyboard.wait('1')
    me=cursor_point()
    pyautogui.moveTo(500, 400,duration = 0.001)
    pyautogui.click(clicks = 1, button ='left', interval = 0.0005)
    pyautogui.moveTo(me[0], me[1],duration = 0.01)

阻塞问题(多线程解决阻塞)

  • 到这里本来键盘触发模拟点击时间已经实现了,但是出现了一个小问题,因为keyboard.wait('1')是阻塞式的,如果没有接收到1程序就不会执行,所以写类似于以下的代码就毫无意义了
keyboard.wait('1')
me=cursor_point()
pyautogui.moveTo(700, 600,duration = 0.01)
pyautogui.click(clicks = 1, button ='left', interval = 0.0005)
pyautogui.moveTo(me[0], me[1],duration = 0.001)

keyboard.wait('2')
me=cursor_point()
pyautogui.moveTo(700, 600,duration = 0.01)
pyautogui.click(clicks = 1, button ='left', interval = 0.0005)
pyautogui.moveTo(me[0], me[1],duration = 0.001)
  • 那么怎么实现监控多个按键呢,朋友问我的时候我的第一反应是多线程,但是灵机一动想到一个好办法(懒办法),再写另一个脚本,让新的脚本监控另一个按键就行了
  • 以上纯属搞笑,总不能监听10个按键就开是个程序吧,下面是最基础的py多线程
  • 开两个线程,t1线程触发名为喵喵的函数,t2线程触发名为汪汪的函数
t1 = threading.Thread(target=喵喵)
t2 = threading.Thread(target=汪汪)
  • 写入主函数,并启动线程
if __name__ == "__main__":
    t1 = threading.Thread(target=喵喵)
    t2 = threading.Thread(target=汪汪)
    t1.start()
    t2.start()
  • 将不同按键监控写入不同线程的函数中,这些函数同时执行,互不干扰,即使阻塞也是在一个线程内阻塞,不会影响到其他线程的监控代码
def 喵喵():
    while 1:
        keyboard.wait('1')
        me=cursor_point()
        pyautogui.moveTo(500, 400,duration = 0.001)
        pyautogui.click(clicks = 1, button ='left', interval = 0.0005)
        pyautogui.moveTo(me[0], me[1],duration = 0.01)
  • 到这里所有问题都解决了,下面是完整代码
import win32gui
import win32con
import win32api
import pyautogui 
import keyboard 
import threading

# 这个函数名可随便定义
def cursor_point():
    pos = win32api.GetCursorPos()
    return int(pos[0]), int(pos[1])

def 喵喵():
    while 1:
        keyboard.wait('1')
        me=cursor_point()
        pyautogui.moveTo(500, 400,duration = 0.001)
        pyautogui.click(clicks = 1, button ='left', interval = 0.0005)
        pyautogui.moveTo(me[0], me[1],duration = 0.01)

def 汪汪():
    while 1:
        keyboard.wait('2')
        me=cursor_point()
        pyautogui.moveTo(700, 600,duration = 0.01)
        pyautogui.click(clicks = 1, button ='left', interval = 0.0005)
        pyautogui.moveTo(me[0], me[1],duration = 0.001)

if __name__ == "__main__":
    t1 = threading.Thread(target=喵喵)
    t2 = threading.Thread(target=汪汪)
    t1.start()
    t2.start()
届ける言葉を今は育ててる
最后更新于 2024-02-07