【问题标题】:Taking Single Click Event and Propagating it to Multiple Areas - python, pynput, pyautogui获取单击事件并将其传播到多个区域 - python、pynput、pyautogui
【发布时间】:2021-07-17 06:06:59
【问题描述】:

无法将 1 次鼠标单击转换为多次鼠标单击。基本上我想做的是一次控制多个窗口。我想点击一个主窗口并将点击传播到后续窗口。在这个 sn-p 中有 4 个窗口,我通过确定它和主窗口之间的 offset 来跟踪它们。

我将 python3 与 pynput 用于鼠标侦听器和 pyautogui 用于鼠标控制。

我遇到的问题是设置鼠标侦听器,使其侦听我的实际点击,但忽略编程点击。现在,我认为它陷入了一个无限循环,我的初始点击触发on_click 事件,传播点击,每个触发一个额外的on_click 事件,传播点击等。当我运行下面的代码时它开始很好,然后当我第一次点击它时,我的鼠标会严重滞后一分钟,然后恢复正常,不再有鼠标监听器处于活动状态。我的猜测是故障保护功能会启动以使其恢复正常。

我尝试过的事情:

  • 使用 pynput 进行侦听和控制 - 这不会改变结果
  • 在传播的点击完成后停止侦听器并创建一个新的侦听器 - 仍然没有改变结果的糟糕的 hacky 解决方案
  • 信号量锁定,_value 在已获取信号量的情况下偷看忽略事件 - 也是 hacky 并且不起作用
  • 通过线程调用 propagateActions 并等待完成,然后从 on_click 事件返回 - 不起作用
  • 注释掉pyautogui.click() - 这允许预期行为将鼠标移动到后续位置并在之后将其返回到其初始位置。无需点击,它就可以完美运行。随着点击,它会滞后并且听众死亡。
  • 搜索 stackoverflow - this question 在结果方面有相似之处,但没有得到答复,并试图实现不同的目标。

我的 sn-p 如下:

from pynput import mouse, keyboard
import pyautogui

pyautogui.PAUSE = 0.01
mouseListener = None
killSwitch = False

# this is just a keyboard listener for a kill switch
def on_release(key):
    if key == keyboard.Key.f1:
        global killSwitch
        print('@@@ Kill switch activated @@@')
        killSwitch = True

# on mouse release I want to propogate a click to 4 other areas
def on_click(x, y, button, pressed):
    print('{0} at {1}'.format('Pressed' if pressed else 'Released', (x, y)))
    if not pressed:
        propogateActions(x, y, button)

# propogates clicks
def propogateActions(x, y, button):
    print('propogating actions to {0} windows'.format(len(offsets)+1))
    for offset in offsets:
        pyautogui.moveTo(x+offset.x, y+offset.y)
        print('mouse moved')
        if button == mouse.Button.left:
            print('left clicking at ({0}, {1})'.format(x+offset.x, y+offset.y))
            pyautogui.click()
    pyautogui.moveTo(x, y)

# point class for ease of use
class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return 'Point(x={0}, y={1})'.format(self.x, self.y)

# main method
def doTheThing():
    print('started')
    while not killSwitch:
        pass

# initializations and starting listeners
# offsets tracks how far the subsequent clicks are from the initial click point
offsets = [Point(50, 0), Point(50, 50), Point(0, 50)]
keyboardListener = keyboard.Listener(on_release=on_release)
mouseListener = mouse.Listener(on_click=on_click)
keyboardListener.start()
mouseListener.start()
doTheThing()

我的问题:

  • 有什么方法可以只监听“真实”点击而不是程序化点击?
  • 如果不是,我可以暂停鼠标侦听器,然后在传播的点击发生后以某种方式重新启动它吗?

这是与当前问题相关的一小部分代码。 offsets 有一个更合适的初始化设置,还有其他花里胡哨,但这是与问题相关的部分。感谢您的帮助。

【问题讨论】:

  • 对于系统来说,所有这些点击都是真实的。
  • 如果您在一个窗口中单击,那么也许您应该仅在单击某个区域时检查鼠标位置和移动。
  • 您还可以测量点击之间的时间,并在另一次点击后几毫秒跳过点击。
  • 是的,我更想问的是用户点击和程序化点击之间是否存在不同之处。从我的程序来看,情况并非如此。对于您的其他 2 个 cmets,这些可以工作,但会限制解决方案。主窗口只能位于固定位置,时间会限制我执行操作的速度。两种脆弱的解决方案,但可以工作。我最终找到了正确的解决方案。在下面回答。

标签: python python-3.x mouselistener pyautogui pynput


【解决方案1】:

找到答案了!必须深入一层。

Pynput 有一个method of suppressing events,它在点击事件后面公开win32 data。对我的一次点击和pyautogui.click() 进行了测试,你瞧,有区别。 data.flags 在用户点击事件上被设置为值 0,在程序化点击上被设置为值 1

这足以让我过滤。这是相关的过滤器:

def win32_event_filter(msg, data):
    if data.flags:
        print('suppressing event')
        return False

将其添加到我上面的代码中并更改了

mouseListener = mouse.Listener(on_click=on_click)

mouseListener = mouse.Listener(on_click=on_click, win32_event_filter=win32_event_filter)

它有效!

我的真实点击占上风,程序化点击被传播,我没有陷入无限循环。如果其他人遇到这个问题,希望这会有所帮助。

【讨论】:

  • 我喜欢它,因为你的层次更深。为您的努力大加 1。
猜你喜欢
  • 2021-03-12
  • 1970-01-01
  • 1970-01-01
  • 2019-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-17
  • 2021-09-10
相关资源
最近更新 更多