moses-palmer / pynput

Sends virtual input commands
GNU Lesser General Public License v3.0
1.8k stars 251 forks source link

Distinguish between real mouse inputs, and pynput-generated events #410

Open josephernest opened 3 years ago

josephernest commented 3 years ago

I'd like to detect real mouse wheelscrolls, and based on timing condition (to detect if there is a fast "acceleration"), then generate additional scrollwheels with pynput. Problem: the real wheelscrolls and the pynput-generated wheelscrolls arrive in the same Listener. Question: Is there a way to distinguish between real and pynput generated wheelscrolls? And avoid that the generated scrolls arrive in the on_scroll listener?

from pynput.mouse import Listener, Controller
mouse = Controller()
def on_scroll(x, y, dx, dy):
    print(x, y, dx, dy)
    if ...:                # condition here based on acceleration detection
        mouse.scroll(0, dy)       # trigger additionnal wheelscrolls --> how to avoid that this generated one will pass into on_scroll listener?
with Listener(on_scroll=on_scroll) as listener:
    listener.join()
moses-palmer commented 3 years ago

The feature-injected branch contains an implementation of this. I have yet to find a way to implement it for Xorg though, so I have not merged it.

I would appreciate your input on the way the feature is introduced though!

nhammond129 commented 2 years ago

I have yet to find a way to implement it for Xorg though

I'm not too familiar with input events, but testing with xev with xfce as my wm:

pynput.keyboard.Controller.press('a') / .release('a') image vs tapping 'a' on my keyboard: image (also xdotool key a shows as synthetic NO)

But interestingly, upon checking with pynput.keyboard.Listener._event_to_key = lambda s, d, e: print(e) I don't see anything printed when I use Controller.press -- only with my hardware keyboard and xdotool. Might be something there?

Just dropping some random investigation on the matter for posterity, and hopefully reviving interest in merging at least the existing support ;)

Tested in a manjaro vm.

FujiwaraChoki commented 2 months ago

Can this be done with the mouse as well?

moses-palmer commented 2 months ago

@FujiwaraChoki, the implementation in the feature branch makes the injected flag available for all types of listeners, so yes.

However, as stated above, I would like some feedback on the feature before merging it. Since last I commented, I have implemented it for X as well.

WilliamStone commented 3 weeks ago

I'd like to detect real mouse wheelscrolls, and based on timing condition (to detect if there is a fast "acceleration"), then generate additional scrollwheels with pynput. Problem: the real wheelscrolls and the pynput-generated wheelscrolls arrive in the same Listener. Question: Is there a way to distinguish between real and pynput generated wheelscrolls? And avoid that the generated scrolls arrive in the on_scroll listener?

I am doing the same thing as OP in Windows and face the same problem. Now my workaround is filter out all scroll events during my generation process, which is barely workable, making it not responding to real scroll events. So I too wish this feature come into release version sooner. Anyway thanks to this excellent library!

WilliamStone commented 2 weeks ago

@moses-palmer Hello!

I tested feature-inject branch in windows, it does differentiates real and generated scroll(mouse wheel) events. And left/right/middle buttons listening for real events also works(I didn't try to generate them, so no generated event listening test), but it crashes when I press xbutton1 or xbutton2. The exception message is as follows:

Unhandled exception in listener callback
Traceback (most recent call last):
  File "D:\Develop\Python\anaconda3\Lib\site-packages\pynput\_util\win32.py", line 386, in _handler
    converted = self._convert(code, msg, lpdata)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Develop\Python\anaconda3\Lib\site-packages\pynput\_util\win32.py", line 401, in _convert
    raise NotImplementedError()
NotImplementedError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "D:\Develop\Python\anaconda3\Lib\site-packages\pynput\_util\__init__.py", line 230, in inner
    return f(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Develop\Python\anaconda3\Lib\site-packages\pynput\_util\win32.py", line 390, in _handler
    self._handle(code, msg, lpdata)
  File "D:\Develop\Python\anaconda3\Lib\site-packages\pynput\mouse\_win32.py", line 221, in _handle
    self.on_click(data.pt.x, data.pt.y, button, pressed)
  File "D:\Develop\Python\anaconda3\Lib\site-packages\pynput\_util\__init__.py", line 146, in inner
    if f(*args) is False:
       ^^^^^^^^
TypeError: on_click() missing 1 required positional argument: 'injected'

The code is plain and simple:

def on_scroll(x, y, dx, dy, injected):
    if injected:
        return
    try:
        event_collector.on_scroll(x, y, dx, dy)
    except Exception as e:
        print(f"Error in on_scroll: {traceback.print_exception(e)}")

def on_click(x, y, button, pressed, injected):
    if pressed:
        event_generator.generator_running = False
    print(f"------------------------------------------------------------------------------------ mouse event: {button} {pressed} ({x}, {y}), {injected}")

with mouse.Listener(on_scroll=on_scroll, on_click=on_click) as listener:
    try:
        listener.join()
    except KeyboardInterrupt:
        print("Stopped listening for scroll events.")

Windows version: 11 23H2 (22635.4082) Python version: 3.12.3

WilliamStone commented 2 weeks ago

I also found that pynput will regard ALL mouse events forwarded from client as injected when running in remote desktop server side, even if the mouse event is triggered from a real mouse at client side. Understandable though, since the mouse events can be seen as injected by remote desktop software. I tested Microsoft remote desktop and NoMachine, both behaves so. Perhaps what we can do is just mention this issue in doc.

moses-palmer commented 2 weeks ago

@WilliamStone, thank you for your feedback.

I have updated the feature branch to fix the error reported here.

Regarding the issue with remote desktop software, I think the observed behaviour is reasonable, but as you suggest, it should be noted in the documentation.