parent
04e441a259
commit
b03d854e90
@ -0,0 +1 @@
|
|||||||
|
pip
|
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Lucas Boppre Niehues
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,62 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: mouse
|
||||||
|
Version: 0.7.1
|
||||||
|
Summary: Hook and simulate mouse events on Windows and Linux
|
||||||
|
Home-page: https://github.com/boppreh/mouse
|
||||||
|
Author: BoppreH
|
||||||
|
Author-email: boppreh@gmail.com
|
||||||
|
License: MIT
|
||||||
|
Keywords: mouse hook simulate hotkey
|
||||||
|
Platform: UNKNOWN
|
||||||
|
Classifier: Development Status :: 4 - Beta
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Operating System :: Microsoft :: Windows
|
||||||
|
Classifier: Operating System :: Unix
|
||||||
|
Classifier: Programming Language :: Python :: 2
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||||
|
Classifier: Topic :: Utilities
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
|
||||||
|
|
||||||
|
mouse
|
||||||
|
=====
|
||||||
|
|
||||||
|
Take full control of your mouse with this small Python library. Hook global events, register hotkeys, simulate mouse movement and clicks, and much more.
|
||||||
|
|
||||||
|
_Huge thanks to [Kirill Pavlov](http://kirillpavlov.com/) for donating the package name. If you are looking for the Cheddargetter.com client implementation, [`pip install mouse==0.5.0`](https://pypi.python.org/pypi/mouse/0.5.0)._
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Global event hook on all mice devices (captures events regardless of focus).
|
||||||
|
- **Listen** and **sends** mouse events.
|
||||||
|
- Works with **Windows** and **Linux** (requires sudo).
|
||||||
|
- **Pure Python**, no C modules to be compiled.
|
||||||
|
- **Zero dependencies**. Trivial to install and deploy, just copy the files.
|
||||||
|
- **Python 2 and 3**.
|
||||||
|
- Includes **high level API** (e.g. [record](#mouse.record) and [play](#mouse.play).
|
||||||
|
- Events automatically captured in separate thread, doesn't block main program.
|
||||||
|
- Tested and documented.
|
||||||
|
|
||||||
|
This program makes no attempt to hide itself, so don't use it for keyloggers.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Install the [PyPI package](https://pypi.python.org/pypi/mouse/):
|
||||||
|
|
||||||
|
$ sudo pip install mouse
|
||||||
|
|
||||||
|
or clone the repository (no installation required, source files are sufficient):
|
||||||
|
|
||||||
|
$ git clone https://github.com/boppreh/mouse
|
||||||
|
|
||||||
|
Then check the [API docs](https://github.com/boppreh/mouse#api) to see what features are available.
|
||||||
|
|
||||||
|
|
||||||
|
## Known limitations:
|
||||||
|
|
||||||
|
- Events generated under Windows don't report device id (`event.device == None`). [#21](https://github.com/boppreh/keyboard/issues/21)
|
||||||
|
- To avoid depending on X the Linux parts reads raw device files (`/dev/input/input*`) but this requries root.
|
||||||
|
- Other applications, such as some games, may register hooks that swallow all key events. In this case `mouse` will be unable to report events.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
mouse-0.7.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
mouse-0.7.1.dist-info/LICENSE.txt,sha256=K_FKUlVV0FqAHC0sKJAVBPt2q-58LX6_tM_HUI198Pc,1077
|
||||||
|
mouse-0.7.1.dist-info/METADATA,sha256=7MmeikMZ3xgUFvcSNMnCnXugXuG0ag8QjFoOV1k9mr0,2455
|
||||||
|
mouse-0.7.1.dist-info/RECORD,,
|
||||||
|
mouse-0.7.1.dist-info/WHEEL,sha256=JZXtYepZFsf4IoivNpnSgKIc4qTHan08DRd56koo_DM,116
|
||||||
|
mouse-0.7.1.dist-info/top_level.txt,sha256=FiA9sXRt9_B8X6je4BlPesL9EakrmGZ0fDKPK09Ql6U,6
|
||||||
|
mouse/__init__.py,sha256=HGaiOxH8PuKnU2oCLN5pEN4yuvxH1JoxT1QXwITFlf0,9147
|
||||||
|
mouse/__main__.py,sha256=Rir1qIanuA6OUsVIywo71MgF1PrVCdZ77F1FPOu4JA0,625
|
||||||
|
mouse/__pycache__/__init__.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/__main__.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_generic.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_mouse_event.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_mouse_tests.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_nixcommon.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_nixmouse.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_winmouse.cpython-37.pyc,,
|
||||||
|
mouse/_generic.py,sha256=STzfL7AUAkcAq6XUyxpQiOYnBnQ1TZjWL1cSFSZ61_o,2132
|
||||||
|
mouse/_mouse_event.py,sha256=zRQGO6M6nbA-jvhI0yz4HzvQNcScF48PZ9iRegcTVjQ,422
|
||||||
|
mouse/_mouse_tests.py,sha256=gNa_NW5sRIsbLMG35ZfXal3eHS2mEpEPhysJRDq0gQA,10000
|
||||||
|
mouse/_nixcommon.py,sha256=FNXiCv7u_A0SzcYJQlYDJBSdIN6IUTVLG4g7oqXgj6o,5552
|
||||||
|
mouse/_nixmouse.py,sha256=3htKn9XkUApFqp3rGCT-ZQIqJFf4iGqoxxoO9MY2x4k,3576
|
||||||
|
mouse/_winmouse.py,sha256=lWAnfGq0etNjFR-vE9e3r9N7Amg5wU957B4ZS3YMTz0,6449
|
@ -0,0 +1,6 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.33.6)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py2-none-any
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
mouse
|
@ -0,0 +1,272 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
mouse
|
||||||
|
=====
|
||||||
|
|
||||||
|
Take full control of your mouse with this small Python library. Hook global events, register hotkeys, simulate mouse movement and clicks, and much more.
|
||||||
|
|
||||||
|
_Huge thanks to [Kirill Pavlov](http://kirillpavlov.com/) for donating the package name. If you are looking for the Cheddargetter.com client implementation, [`pip install mouse==0.5.0`](https://pypi.python.org/pypi/mouse/0.5.0)._
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Global event hook on all mice devices (captures events regardless of focus).
|
||||||
|
- **Listen** and **sends** mouse events.
|
||||||
|
- Works with **Windows** and **Linux** (requires sudo).
|
||||||
|
- **Pure Python**, no C modules to be compiled.
|
||||||
|
- **Zero dependencies**. Trivial to install and deploy, just copy the files.
|
||||||
|
- **Python 2 and 3**.
|
||||||
|
- Includes **high level API** (e.g. [record](#mouse.record) and [play](#mouse.play).
|
||||||
|
- Events automatically captured in separate thread, doesn't block main program.
|
||||||
|
- Tested and documented.
|
||||||
|
|
||||||
|
This program makes no attempt to hide itself, so don't use it for keyloggers.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Install the [PyPI package](https://pypi.python.org/pypi/mouse/):
|
||||||
|
|
||||||
|
$ sudo pip install mouse
|
||||||
|
|
||||||
|
or clone the repository (no installation required, source files are sufficient):
|
||||||
|
|
||||||
|
$ git clone https://github.com/boppreh/mouse
|
||||||
|
|
||||||
|
Then check the [API docs](https://github.com/boppreh/mouse#api) to see what features are available.
|
||||||
|
|
||||||
|
|
||||||
|
## Known limitations:
|
||||||
|
|
||||||
|
- Events generated under Windows don't report device id (`event.device == None`). [#21](https://github.com/boppreh/keyboard/issues/21)
|
||||||
|
- To avoid depending on X the Linux parts reads raw device files (`/dev/input/input*`) but this requries root.
|
||||||
|
- Other applications, such as some games, may register hooks that swallow all key events. In this case `mouse` will be unable to report events.
|
||||||
|
"""
|
||||||
|
# TODO
|
||||||
|
# - infinite wait
|
||||||
|
# - mouse.on_move
|
||||||
|
version = '0.7.1'
|
||||||
|
|
||||||
|
import time as _time
|
||||||
|
|
||||||
|
import platform as _platform
|
||||||
|
if _platform.system() == 'Windows':
|
||||||
|
from. import _winmouse as _os_mouse
|
||||||
|
elif _platform.system() == 'Linux':
|
||||||
|
from. import _nixmouse as _os_mouse
|
||||||
|
else:
|
||||||
|
raise OSError("Unsupported platform '{}'".format(_platform.system()))
|
||||||
|
|
||||||
|
from ._mouse_event import ButtonEvent, MoveEvent, WheelEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE
|
||||||
|
from ._generic import GenericListener as _GenericListener
|
||||||
|
|
||||||
|
_pressed_events = set()
|
||||||
|
class _MouseListener(_GenericListener):
|
||||||
|
def init(self):
|
||||||
|
_os_mouse.init()
|
||||||
|
def pre_process_event(self, event):
|
||||||
|
if isinstance(event, ButtonEvent):
|
||||||
|
if event.event_type in (UP, DOUBLE):
|
||||||
|
_pressed_events.discard(event.button)
|
||||||
|
else:
|
||||||
|
_pressed_events.add(event.button)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def listen(self):
|
||||||
|
_os_mouse.listen(self.queue)
|
||||||
|
|
||||||
|
_listener = _MouseListener()
|
||||||
|
|
||||||
|
def is_pressed(button=LEFT):
|
||||||
|
""" Returns True if the given button is currently pressed. """
|
||||||
|
_listener.start_if_necessary()
|
||||||
|
return button in _pressed_events
|
||||||
|
|
||||||
|
def press(button=LEFT):
|
||||||
|
""" Presses the given button (but doesn't release). """
|
||||||
|
_os_mouse.press(button)
|
||||||
|
|
||||||
|
def release(button=LEFT):
|
||||||
|
""" Releases the given button. """
|
||||||
|
_os_mouse.release(button)
|
||||||
|
|
||||||
|
def click(button=LEFT):
|
||||||
|
""" Sends a click with the given button. """
|
||||||
|
_os_mouse.press(button)
|
||||||
|
_os_mouse.release(button)
|
||||||
|
|
||||||
|
def double_click(button=LEFT):
|
||||||
|
""" Sends a double click with the given button. """
|
||||||
|
click(button)
|
||||||
|
click(button)
|
||||||
|
|
||||||
|
def right_click():
|
||||||
|
""" Sends a right click with the given button. """
|
||||||
|
click(RIGHT)
|
||||||
|
|
||||||
|
def wheel(delta=1):
|
||||||
|
""" Scrolls the wheel `delta` clicks. Sign indicates direction. """
|
||||||
|
_os_mouse.wheel(delta)
|
||||||
|
|
||||||
|
def move(x, y, absolute=True, duration=0):
|
||||||
|
"""
|
||||||
|
Moves the mouse. If `absolute`, to position (x, y), otherwise move relative
|
||||||
|
to the current position. If `duration` is non-zero, animates the movement.
|
||||||
|
"""
|
||||||
|
x = int(x)
|
||||||
|
y = int(y)
|
||||||
|
|
||||||
|
# Requires an extra system call on Linux, but `move_relative` is measured
|
||||||
|
# in millimiters so we would lose precision.
|
||||||
|
position_x, position_y = get_position()
|
||||||
|
|
||||||
|
if not absolute:
|
||||||
|
x = position_x + x
|
||||||
|
y = position_y + y
|
||||||
|
|
||||||
|
if duration:
|
||||||
|
start_x = position_x
|
||||||
|
start_y = position_y
|
||||||
|
dx = x - start_x
|
||||||
|
dy = y - start_y
|
||||||
|
|
||||||
|
if dx == 0 and dy == 0:
|
||||||
|
_time.sleep(duration)
|
||||||
|
else:
|
||||||
|
# 120 movements per second.
|
||||||
|
# Round and keep float to ensure float division in Python 2
|
||||||
|
steps = max(1.0, float(int(duration * 120.0)))
|
||||||
|
for i in range(int(steps)+1):
|
||||||
|
move(start_x + dx*i/steps, start_y + dy*i/steps)
|
||||||
|
_time.sleep(duration/steps)
|
||||||
|
else:
|
||||||
|
_os_mouse.move_to(x, y)
|
||||||
|
|
||||||
|
def drag(start_x, start_y, end_x, end_y, absolute=True, duration=0):
|
||||||
|
"""
|
||||||
|
Holds the left mouse button, moving from start to end position, then
|
||||||
|
releases. `absolute` and `duration` are parameters regarding the mouse
|
||||||
|
movement.
|
||||||
|
"""
|
||||||
|
if is_pressed():
|
||||||
|
release()
|
||||||
|
move(start_x, start_y, absolute, 0)
|
||||||
|
press()
|
||||||
|
move(end_x, end_y, absolute, duration)
|
||||||
|
release()
|
||||||
|
|
||||||
|
def on_button(callback, args=(), buttons=(LEFT, MIDDLE, RIGHT, X, X2), types=(UP, DOWN, DOUBLE)):
|
||||||
|
""" Invokes `callback` with `args` when the specified event happens. """
|
||||||
|
if not isinstance(buttons, (tuple, list)):
|
||||||
|
buttons = (buttons,)
|
||||||
|
if not isinstance(types, (tuple, list)):
|
||||||
|
types = (types,)
|
||||||
|
|
||||||
|
def handler(event):
|
||||||
|
if isinstance(event, ButtonEvent):
|
||||||
|
if event.event_type in types and event.button in buttons:
|
||||||
|
callback(*args)
|
||||||
|
_listener.add_handler(handler)
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def on_click(callback, args=()):
|
||||||
|
""" Invokes `callback` with `args` when the left button is clicked. """
|
||||||
|
return on_button(callback, args, [LEFT], [UP])
|
||||||
|
|
||||||
|
def on_double_click(callback, args=()):
|
||||||
|
"""
|
||||||
|
Invokes `callback` with `args` when the left button is double clicked.
|
||||||
|
"""
|
||||||
|
return on_button(callback, args, [LEFT], [DOUBLE])
|
||||||
|
|
||||||
|
def on_right_click(callback, args=()):
|
||||||
|
""" Invokes `callback` with `args` when the right button is clicked. """
|
||||||
|
return on_button(callback, args, [RIGHT], [UP])
|
||||||
|
|
||||||
|
def on_middle_click(callback, args=()):
|
||||||
|
""" Invokes `callback` with `args` when the middle button is clicked. """
|
||||||
|
return on_button(callback, args, [MIDDLE], [UP])
|
||||||
|
|
||||||
|
def wait(button=LEFT, target_types=(UP, DOWN, DOUBLE)):
|
||||||
|
"""
|
||||||
|
Blocks program execution until the given button performs an event.
|
||||||
|
"""
|
||||||
|
from threading import Lock
|
||||||
|
lock = Lock()
|
||||||
|
lock.acquire()
|
||||||
|
handler = on_button(lock.release, (), [button], target_types)
|
||||||
|
lock.acquire()
|
||||||
|
_listener.remove_handler(handler)
|
||||||
|
|
||||||
|
def get_position():
|
||||||
|
""" Returns the (x, y) mouse position. """
|
||||||
|
return _os_mouse.get_position()
|
||||||
|
|
||||||
|
def hook(callback):
|
||||||
|
"""
|
||||||
|
Installs a global listener on all available mouses, invoking `callback`
|
||||||
|
each time it is moved, a key status changes or the wheel is spun. A mouse
|
||||||
|
event is passed as argument, with type either `mouse.ButtonEvent`,
|
||||||
|
`mouse.WheelEvent` or `mouse.MoveEvent`.
|
||||||
|
|
||||||
|
Returns the given callback for easier development.
|
||||||
|
"""
|
||||||
|
_listener.add_handler(callback)
|
||||||
|
return callback
|
||||||
|
|
||||||
|
def unhook(callback):
|
||||||
|
"""
|
||||||
|
Removes a previously installed hook.
|
||||||
|
"""
|
||||||
|
_listener.remove_handler(callback)
|
||||||
|
|
||||||
|
def unhook_all():
|
||||||
|
"""
|
||||||
|
Removes all hooks registered by this application. Note this may include
|
||||||
|
hooks installed by high level functions, such as `record`.
|
||||||
|
"""
|
||||||
|
del _listener.handlers[:]
|
||||||
|
|
||||||
|
def record(button=RIGHT, target_types=(DOWN,)):
|
||||||
|
"""
|
||||||
|
Records all mouse events until the user presses the given button.
|
||||||
|
Then returns the list of events recorded. Pairs well with `play(events)`.
|
||||||
|
|
||||||
|
Note: this is a blocking function.
|
||||||
|
Note: for more details on the mouse hook and events see `hook`.
|
||||||
|
"""
|
||||||
|
recorded = []
|
||||||
|
hook(recorded.append)
|
||||||
|
wait(button=button, target_types=target_types)
|
||||||
|
unhook(recorded.append)
|
||||||
|
return recorded
|
||||||
|
|
||||||
|
def play(events, speed_factor=1.0, include_clicks=True, include_moves=True, include_wheel=True):
|
||||||
|
"""
|
||||||
|
Plays a sequence of recorded events, maintaining the relative time
|
||||||
|
intervals. If speed_factor is <= 0 then the actions are replayed as fast
|
||||||
|
as the OS allows. Pairs well with `record()`.
|
||||||
|
|
||||||
|
The parameters `include_*` define if events of that type should be inluded
|
||||||
|
in the replay or ignored.
|
||||||
|
"""
|
||||||
|
last_time = None
|
||||||
|
for event in events:
|
||||||
|
if speed_factor > 0 and last_time is not None:
|
||||||
|
_time.sleep((event.time - last_time) / speed_factor)
|
||||||
|
last_time = event.time
|
||||||
|
|
||||||
|
if isinstance(event, ButtonEvent) and include_clicks:
|
||||||
|
if event.event_type == UP:
|
||||||
|
_os_mouse.release(event.button)
|
||||||
|
else:
|
||||||
|
_os_mouse.press(event.button)
|
||||||
|
elif isinstance(event, MoveEvent) and include_moves:
|
||||||
|
_os_mouse.move_to(event.x, event.y)
|
||||||
|
elif isinstance(event, WheelEvent) and include_wheel:
|
||||||
|
_os_mouse.wheel(event.delta)
|
||||||
|
|
||||||
|
replay = play
|
||||||
|
hold = press
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('Recording... Double click to stop and replay.')
|
||||||
|
play(record())
|
@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import mouse
|
||||||
|
import fileinput
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class_by_name = {
|
||||||
|
'ButtonEvent': mouse.ButtonEvent,
|
||||||
|
'WheelEvent': mouse.WheelEvent,
|
||||||
|
'MoveEvent': mouse.MoveEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_event_json(event):
|
||||||
|
# Could use json.dumps(event.__dict__()), but this way we guarantee semantic order.
|
||||||
|
d = event._asdict()
|
||||||
|
d['event_class'] = event.__class__.__name__
|
||||||
|
print(json.dumps(d))
|
||||||
|
sys.stdout.flush()
|
||||||
|
mouse.hook(print_event_json)
|
||||||
|
|
||||||
|
def load(line):
|
||||||
|
d = json.loads(line)
|
||||||
|
class_ = class_by_name[d['event_class']]
|
||||||
|
del d['event_class']
|
||||||
|
return class_(**d)
|
||||||
|
|
||||||
|
mouse.play(load(line) for line in fileinput.input())
|
@ -0,0 +1,72 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from threading import Thread, Lock
|
||||||
|
import traceback
|
||||||
|
import functools
|
||||||
|
|
||||||
|
try:
|
||||||
|
from queue import Queue
|
||||||
|
except ImportError:
|
||||||
|
from Queue import Queue
|
||||||
|
|
||||||
|
class GenericListener(object):
|
||||||
|
lock = Lock()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.handlers = []
|
||||||
|
self.listening = False
|
||||||
|
self.queue = Queue()
|
||||||
|
|
||||||
|
def invoke_handlers(self, event):
|
||||||
|
for handler in self.handlers:
|
||||||
|
try:
|
||||||
|
if handler(event):
|
||||||
|
# Stop processing this hotkey.
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def start_if_necessary(self):
|
||||||
|
"""
|
||||||
|
Starts the listening thread if it wasn't already.
|
||||||
|
"""
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
if not self.listening:
|
||||||
|
self.init()
|
||||||
|
|
||||||
|
self.listening = True
|
||||||
|
self.listening_thread = Thread(target=self.listen)
|
||||||
|
self.listening_thread.daemon = True
|
||||||
|
self.listening_thread.start()
|
||||||
|
|
||||||
|
self.processing_thread = Thread(target=self.process)
|
||||||
|
self.processing_thread.daemon = True
|
||||||
|
self.processing_thread.start()
|
||||||
|
finally:
|
||||||
|
self.lock.release()
|
||||||
|
|
||||||
|
def pre_process_event(self, event):
|
||||||
|
raise NotImplementedError('This method should be implemented in the child class.')
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
"""
|
||||||
|
Loops over the underlying queue of events and processes them in order.
|
||||||
|
"""
|
||||||
|
assert self.queue is not None
|
||||||
|
while True:
|
||||||
|
event = self.queue.get()
|
||||||
|
if self.pre_process_event(event):
|
||||||
|
self.invoke_handlers(event)
|
||||||
|
self.queue.task_done()
|
||||||
|
|
||||||
|
def add_handler(self, handler):
|
||||||
|
"""
|
||||||
|
Adds a function to receive each event captured, starting the capturing
|
||||||
|
process if necessary.
|
||||||
|
"""
|
||||||
|
self.start_if_necessary()
|
||||||
|
self.handlers.append(handler)
|
||||||
|
|
||||||
|
def remove_handler(self, handler):
|
||||||
|
""" Removes a previously added event handler. """
|
||||||
|
self.handlers.remove(handler)
|
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
LEFT = 'left'
|
||||||
|
RIGHT = 'right'
|
||||||
|
MIDDLE = 'middle'
|
||||||
|
WHEEL = 'wheel'
|
||||||
|
X = 'x'
|
||||||
|
X2 = 'x2'
|
||||||
|
|
||||||
|
UP = 'up'
|
||||||
|
DOWN = 'down'
|
||||||
|
DOUBLE = 'double'
|
||||||
|
VERTICAL = 'vertical'
|
||||||
|
HORIZONTAL = 'horizontal'
|
||||||
|
|
||||||
|
|
||||||
|
ButtonEvent = namedtuple('ButtonEvent', ['event_type', 'button', 'time'])
|
||||||
|
WheelEvent = namedtuple('WheelEvent', ['delta', 'time'])
|
||||||
|
MoveEvent = namedtuple('MoveEvent', ['x', 'y', 'time'])
|
@ -0,0 +1,271 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import unittest
|
||||||
|
import time
|
||||||
|
|
||||||
|
from ._mouse_event import MoveEvent, ButtonEvent, WheelEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE
|
||||||
|
import mouse
|
||||||
|
|
||||||
|
class FakeOsMouse(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.append = None
|
||||||
|
self.position = (0, 0)
|
||||||
|
self.queue = None
|
||||||
|
self.init = lambda: None
|
||||||
|
|
||||||
|
def listen(self, queue):
|
||||||
|
self.listening = True
|
||||||
|
self.queue = queue
|
||||||
|
|
||||||
|
def press(self, button):
|
||||||
|
self.append((DOWN, button))
|
||||||
|
|
||||||
|
def release(self, button):
|
||||||
|
self.append((UP, button))
|
||||||
|
|
||||||
|
def get_position(self):
|
||||||
|
return self.position
|
||||||
|
|
||||||
|
def move_to(self, x, y):
|
||||||
|
self.append(('move', (x, y)))
|
||||||
|
self.position = (x, y)
|
||||||
|
|
||||||
|
def wheel(self, delta):
|
||||||
|
self.append(('wheel', delta))
|
||||||
|
|
||||||
|
def move_relative(self, x, y):
|
||||||
|
self.position = (self.position[0] + x, self.position[1] + y)
|
||||||
|
|
||||||
|
class TestMouse(unittest.TestCase):
|
||||||
|
@staticmethod
|
||||||
|
def setUpClass():
|
||||||
|
mouse._os_mouse= FakeOsMouse()
|
||||||
|
mouse._listener.start_if_necessary()
|
||||||
|
assert mouse._os_mouse.listening
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.events = []
|
||||||
|
mouse._pressed_events.clear()
|
||||||
|
mouse._os_mouse.append = self.events.append
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
mouse.unhook_all()
|
||||||
|
# Make sure there's no spill over between tests.
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def wait_for_events_queue(self):
|
||||||
|
mouse._listener.queue.join()
|
||||||
|
|
||||||
|
def flush_events(self):
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
events = list(self.events)
|
||||||
|
# Ugly, but requried to work in Python2. Python3 has list.clear
|
||||||
|
del self.events[:]
|
||||||
|
return events
|
||||||
|
|
||||||
|
def press(self, button=LEFT):
|
||||||
|
mouse._os_mouse.queue.put(ButtonEvent(DOWN, button, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def release(self, button=LEFT):
|
||||||
|
mouse._os_mouse.queue.put(ButtonEvent(UP, button, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def double_click(self, button=LEFT):
|
||||||
|
mouse._os_mouse.queue.put(ButtonEvent(DOUBLE, button, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def click(self, button=LEFT):
|
||||||
|
self.press(button)
|
||||||
|
self.release(button)
|
||||||
|
|
||||||
|
def wheel(self, delta=1):
|
||||||
|
mouse._os_mouse.queue.put(WheelEvent(delta, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def move(self, x=0, y=0):
|
||||||
|
mouse._os_mouse.queue.put(MoveEvent(x, y, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def test_hook(self):
|
||||||
|
events = []
|
||||||
|
self.press()
|
||||||
|
mouse.hook(events.append)
|
||||||
|
self.press()
|
||||||
|
mouse.unhook(events.append)
|
||||||
|
self.press()
|
||||||
|
self.assertEqual(len(events), 1)
|
||||||
|
|
||||||
|
def test_is_pressed(self):
|
||||||
|
self.assertFalse(mouse.is_pressed())
|
||||||
|
self.press()
|
||||||
|
self.assertTrue(mouse.is_pressed())
|
||||||
|
self.release()
|
||||||
|
self.press(X2)
|
||||||
|
self.assertFalse(mouse.is_pressed())
|
||||||
|
|
||||||
|
self.assertTrue(mouse.is_pressed(X2))
|
||||||
|
self.press(X2)
|
||||||
|
self.assertTrue(mouse.is_pressed(X2))
|
||||||
|
self.release(X2)
|
||||||
|
self.release(X2)
|
||||||
|
self.assertFalse(mouse.is_pressed(X2))
|
||||||
|
|
||||||
|
def test_buttons(self):
|
||||||
|
mouse.press()
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, LEFT)])
|
||||||
|
mouse.release()
|
||||||
|
self.assertEqual(self.flush_events(), [(UP, LEFT)])
|
||||||
|
mouse.click()
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, LEFT), (UP, LEFT)])
|
||||||
|
mouse.double_click()
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, LEFT), (UP, LEFT), (DOWN, LEFT), (UP, LEFT)])
|
||||||
|
mouse.right_click()
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, RIGHT), (UP, RIGHT)])
|
||||||
|
mouse.click(RIGHT)
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, RIGHT), (UP, RIGHT)])
|
||||||
|
mouse.press(X2)
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, X2)])
|
||||||
|
|
||||||
|
def test_position(self):
|
||||||
|
self.assertEqual(mouse.get_position(), mouse._os_mouse.get_position())
|
||||||
|
|
||||||
|
def test_move(self):
|
||||||
|
mouse.move(0, 0)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (0, 0))
|
||||||
|
mouse.move(100, 500)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (100, 500))
|
||||||
|
mouse.move(1, 2, False)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (101, 502))
|
||||||
|
|
||||||
|
mouse.move(0, 0)
|
||||||
|
mouse.move(100, 499, True, duration=0.01)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (100, 499))
|
||||||
|
mouse.move(100, 1, False, duration=0.01)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (200, 500))
|
||||||
|
mouse.move(0, 0, False, duration=0.01)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (200, 500))
|
||||||
|
|
||||||
|
def triggers(self, fn, events, **kwargs):
|
||||||
|
self.triggered = False
|
||||||
|
def callback():
|
||||||
|
self.triggered = True
|
||||||
|
handler = fn(callback, **kwargs)
|
||||||
|
|
||||||
|
for event_type, arg in events:
|
||||||
|
if event_type == DOWN:
|
||||||
|
self.press(arg)
|
||||||
|
elif event_type == UP:
|
||||||
|
self.release(arg)
|
||||||
|
elif event_type == DOUBLE:
|
||||||
|
self.double_click(arg)
|
||||||
|
elif event_type == 'WHEEL':
|
||||||
|
self.wheel()
|
||||||
|
|
||||||
|
mouse._listener.remove_handler(handler)
|
||||||
|
return self.triggered
|
||||||
|
|
||||||
|
def test_on_button(self):
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, LEFT)]))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, RIGHT)]))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, X)]))
|
||||||
|
|
||||||
|
self.assertFalse(self.triggers(mouse.on_button, [('WHEEL', '')]))
|
||||||
|
|
||||||
|
self.assertFalse(self.triggers(mouse.on_button, [(DOWN, X)], buttons=MIDDLE))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE, types=UP))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(UP, MIDDLE)], buttons=MIDDLE, types=UP))
|
||||||
|
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(UP, MIDDLE)], buttons=[MIDDLE, LEFT], types=[UP, DOWN]))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, LEFT)], buttons=[MIDDLE, LEFT], types=[UP, DOWN]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_button, [(UP, X)], buttons=[MIDDLE, LEFT], types=[UP, DOWN]))
|
||||||
|
|
||||||
|
def test_ons(self):
|
||||||
|
self.assertTrue(self.triggers(mouse.on_click, [(UP, LEFT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_click, [(UP, RIGHT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_click, [(DOWN, LEFT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_click, [(DOWN, RIGHT)]))
|
||||||
|
|
||||||
|
self.assertTrue(self.triggers(mouse.on_double_click, [(DOUBLE, LEFT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_double_click, [(DOUBLE, RIGHT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_double_click, [(DOWN, RIGHT)]))
|
||||||
|
|
||||||
|
self.assertTrue(self.triggers(mouse.on_right_click, [(UP, RIGHT)]))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_middle_click, [(UP, MIDDLE)]))
|
||||||
|
|
||||||
|
def test_wait(self):
|
||||||
|
# If this fails it blocks. Unfortunately, but I see no other way of testing.
|
||||||
|
from threading import Thread, Lock
|
||||||
|
lock = Lock()
|
||||||
|
lock.acquire()
|
||||||
|
def t():
|
||||||
|
mouse.wait()
|
||||||
|
lock.release()
|
||||||
|
Thread(target=t).start()
|
||||||
|
self.press()
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
def test_record_play(self):
|
||||||
|
from threading import Thread, Lock
|
||||||
|
lock = Lock()
|
||||||
|
lock.acquire()
|
||||||
|
def t():
|
||||||
|
self.recorded = mouse.record(RIGHT)
|
||||||
|
lock.release()
|
||||||
|
Thread(target=t).start()
|
||||||
|
self.click()
|
||||||
|
self.wheel(5)
|
||||||
|
self.move(100, 50)
|
||||||
|
self.press(RIGHT)
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
self.assertEqual(len(self.recorded), 5)
|
||||||
|
self.assertEqual(self.recorded[0]._replace(time=None), ButtonEvent(DOWN, LEFT, None))
|
||||||
|
self.assertEqual(self.recorded[1]._replace(time=None), ButtonEvent(UP, LEFT, None))
|
||||||
|
self.assertEqual(self.recorded[2]._replace(time=None), WheelEvent(5, None))
|
||||||
|
self.assertEqual(self.recorded[3]._replace(time=None), MoveEvent(100, 50, None))
|
||||||
|
self.assertEqual(self.recorded[4]._replace(time=None), ButtonEvent(DOWN, RIGHT, None))
|
||||||
|
|
||||||
|
mouse.play(self.recorded, speed_factor=0)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 5)
|
||||||
|
self.assertEqual(events[0], (DOWN, LEFT))
|
||||||
|
self.assertEqual(events[1], (UP, LEFT))
|
||||||
|
self.assertEqual(events[2], ('wheel', 5))
|
||||||
|
self.assertEqual(events[3], ('move', (100, 50)))
|
||||||
|
self.assertEqual(events[4], (DOWN, RIGHT))
|
||||||
|
|
||||||
|
mouse.play(self.recorded)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 5)
|
||||||
|
self.assertEqual(events[0], (DOWN, LEFT))
|
||||||
|
self.assertEqual(events[1], (UP, LEFT))
|
||||||
|
self.assertEqual(events[2], ('wheel', 5))
|
||||||
|
self.assertEqual(events[3], ('move', (100, 50)))
|
||||||
|
self.assertEqual(events[4], (DOWN, RIGHT))
|
||||||
|
|
||||||
|
mouse.play(self.recorded, include_clicks=False)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 2)
|
||||||
|
self.assertEqual(events[0], ('wheel', 5))
|
||||||
|
self.assertEqual(events[1], ('move', (100, 50)))
|
||||||
|
|
||||||
|
mouse.play(self.recorded, include_moves=False)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 4)
|
||||||
|
self.assertEqual(events[0], (DOWN, LEFT))
|
||||||
|
self.assertEqual(events[1], (UP, LEFT))
|
||||||
|
self.assertEqual(events[2], ('wheel', 5))
|
||||||
|
self.assertEqual(events[3], (DOWN, RIGHT))
|
||||||
|
|
||||||
|
mouse.play(self.recorded, include_wheel=False)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 4)
|
||||||
|
self.assertEqual(events[0], (DOWN, LEFT))
|
||||||
|
self.assertEqual(events[1], (UP, LEFT))
|
||||||
|
self.assertEqual(events[2], ('move', (100, 50)))
|
||||||
|
self.assertEqual(events[3], (DOWN, RIGHT))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -0,0 +1,165 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import struct
|
||||||
|
import os
|
||||||
|
import atexit
|
||||||
|
from time import time as now
|
||||||
|
from threading import Thread
|
||||||
|
from glob import glob
|
||||||
|
try:
|
||||||
|
from queue import Queue
|
||||||
|
except ImportError:
|
||||||
|
from Queue import Queue
|
||||||
|
|
||||||
|
event_bin_format = 'llHHI'
|
||||||
|
|
||||||
|
# Taken from include/linux/input.h
|
||||||
|
# https://www.kernel.org/doc/Documentation/input/event-codes.txt
|
||||||
|
EV_SYN = 0x00
|
||||||
|
EV_KEY = 0x01
|
||||||
|
EV_REL = 0x02
|
||||||
|
EV_ABS = 0x03
|
||||||
|
EV_MSC = 0x04
|
||||||
|
|
||||||
|
def make_uinput():
|
||||||
|
import fcntl, struct
|
||||||
|
|
||||||
|
# Requires uinput driver, but it's usually available.
|
||||||
|
uinput = open("/dev/uinput", 'wb')
|
||||||
|
UI_SET_EVBIT = 0x40045564
|
||||||
|
fcntl.ioctl(uinput, UI_SET_EVBIT, EV_KEY)
|
||||||
|
|
||||||
|
UI_SET_KEYBIT = 0x40045565
|
||||||
|
for i in range(256):
|
||||||
|
fcntl.ioctl(uinput, UI_SET_KEYBIT, i)
|
||||||
|
|
||||||
|
BUS_USB = 0x03
|
||||||
|
uinput_user_dev = "80sHHHHi64i64i64i64i"
|
||||||
|
axis = [0] * 64 * 4
|
||||||
|
uinput.write(struct.pack(uinput_user_dev, b"Virtual Keyboard", BUS_USB, 1, 1, 1, 0, *axis))
|
||||||
|
uinput.flush() # Without this you may get Errno 22: Invalid argument.
|
||||||
|
|
||||||
|
UI_DEV_CREATE = 0x5501
|
||||||
|
fcntl.ioctl(uinput, UI_DEV_CREATE)
|
||||||
|
UI_DEV_DESTROY = 0x5502
|
||||||
|
#fcntl.ioctl(uinput, UI_DEV_DESTROY)
|
||||||
|
|
||||||
|
return uinput
|
||||||
|
|
||||||
|
class EventDevice(object):
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
self._input_file = None
|
||||||
|
self._output_file = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def input_file(self):
|
||||||
|
if self._input_file is None:
|
||||||
|
try:
|
||||||
|
self._input_file = open(self.path, 'rb')
|
||||||
|
except IOError as e:
|
||||||
|
if e.strerror == 'Permission denied':
|
||||||
|
print('Permission denied ({}). You must be sudo to access global events.'.format(self.path))
|
||||||
|
exit()
|
||||||
|
|
||||||
|
def try_close():
|
||||||
|
try:
|
||||||
|
self._input_file.close
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
atexit.register(try_close)
|
||||||
|
return self._input_file
|
||||||
|
|
||||||
|
@property
|
||||||
|
def output_file(self):
|
||||||
|
if self._output_file is None:
|
||||||
|
self._output_file = open(self.path, 'wb')
|
||||||
|
atexit.register(self._output_file.close)
|
||||||
|
return self._output_file
|
||||||
|
|
||||||
|
def read_event(self):
|
||||||
|
data = self.input_file.read(struct.calcsize(event_bin_format))
|
||||||
|
seconds, microseconds, type, code, value = struct.unpack(event_bin_format, data)
|
||||||
|
return seconds + microseconds / 1e6, type, code, value, self.path
|
||||||
|
|
||||||
|
def write_event(self, type, code, value):
|
||||||
|
integer, fraction = divmod(now(), 1)
|
||||||
|
seconds = int(integer)
|
||||||
|
microseconds = int(fraction * 1e6)
|
||||||
|
data_event = struct.pack(event_bin_format, seconds, microseconds, type, code, value)
|
||||||
|
|
||||||
|
# Send a sync event to ensure other programs update.
|
||||||
|
sync_event = struct.pack(event_bin_format, seconds, microseconds, EV_SYN, 0, 0)
|
||||||
|
|
||||||
|
self.output_file.write(data_event + sync_event)
|
||||||
|
self.output_file.flush()
|
||||||
|
|
||||||
|
class AggregatedEventDevice(object):
|
||||||
|
def __init__(self, devices, output=None):
|
||||||
|
self.event_queue = Queue()
|
||||||
|
self.devices = devices
|
||||||
|
self.output = output or self.devices[0]
|
||||||
|
def start_reading(device):
|
||||||
|
while True:
|
||||||
|
self.event_queue.put(device.read_event())
|
||||||
|
for device in self.devices:
|
||||||
|
thread = Thread(target=start_reading, args=[device])
|
||||||
|
thread.setDaemon(True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def read_event(self):
|
||||||
|
return self.event_queue.get(block=True)
|
||||||
|
|
||||||
|
def write_event(self, type, code, value):
|
||||||
|
self.output.write_event(type, code, value)
|
||||||
|
|
||||||
|
import re
|
||||||
|
from collections import namedtuple
|
||||||
|
DeviceDescription = namedtuple('DeviceDescription', 'event_file is_mouse is_keyboard')
|
||||||
|
device_pattern = r"""N: Name="([^"]+?)".+?H: Handlers=([^\n]+)"""
|
||||||
|
def list_devices_from_proc(type_name):
|
||||||
|
try:
|
||||||
|
with open('/proc/bus/input/devices') as f:
|
||||||
|
description = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return
|
||||||
|
|
||||||
|
devices = {}
|
||||||
|
for name, handlers in re.findall(device_pattern, description, re.DOTALL):
|
||||||
|
path = '/dev/input/event' + re.search(r'event(\d+)', handlers).group(1)
|
||||||
|
if type_name in handlers:
|
||||||
|
yield EventDevice(path)
|
||||||
|
|
||||||
|
def list_devices_from_by_id(type_name):
|
||||||
|
for path in glob('/dev/input/by-id/*-event-' + type_name):
|
||||||
|
yield EventDevice(path)
|
||||||
|
|
||||||
|
def aggregate_devices(type_name):
|
||||||
|
# Some systems have multiple keyboards with different range of allowed keys
|
||||||
|
# on each one, like a notebook with a "keyboard" device exclusive for the
|
||||||
|
# power button. Instead of figuring out which keyboard allows which key to
|
||||||
|
# send events, we create a fake device and send all events through there.
|
||||||
|
uinput = make_uinput()
|
||||||
|
fake_device = EventDevice('uinput Fake Device')
|
||||||
|
fake_device._input_file = uinput
|
||||||
|
fake_device._output_file = uinput
|
||||||
|
|
||||||
|
# We don't aggregate devices from different sources to avoid
|
||||||
|
# duplicates.
|
||||||
|
|
||||||
|
devices_from_proc = list(list_devices_from_proc(type_name))
|
||||||
|
if devices_from_proc:
|
||||||
|
return AggregatedEventDevice(devices_from_proc, output=fake_device)
|
||||||
|
|
||||||
|
# breaks on mouse for virtualbox
|
||||||
|
# was getting /dev/input/by-id/usb-VirtualBox_USB_Tablet-event-mouse
|
||||||
|
devices_from_by_id = list(list_devices_from_by_id(type_name))
|
||||||
|
if devices_from_by_id:
|
||||||
|
return AggregatedEventDevice(devices_from_by_id, output=fake_device)
|
||||||
|
|
||||||
|
# If no keyboards were found we can only use the fake device to send keys.
|
||||||
|
return fake_device
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_root():
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
raise ImportError('You must be root to use this library on linux.')
|
@ -0,0 +1,131 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import struct
|
||||||
|
from subprocess import check_output
|
||||||
|
import re
|
||||||
|
from ._nixcommon import EV_KEY, EV_REL, EV_MSC, EV_SYN, EV_ABS, aggregate_devices, ensure_root
|
||||||
|
from ._mouse_event import ButtonEvent, WheelEvent, MoveEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
from ctypes import c_uint32, c_uint, c_int, c_void_p, byref
|
||||||
|
|
||||||
|
display = None
|
||||||
|
window = None
|
||||||
|
x11 = None
|
||||||
|
def build_display():
|
||||||
|
global display, window, x11
|
||||||
|
if display and window and x11: return
|
||||||
|
x11 = ctypes.cdll.LoadLibrary(ctypes.util.find_library('X11'))
|
||||||
|
# Required because we will have multiple threads calling x11,
|
||||||
|
# such as the listener thread and then main using "move_to".
|
||||||
|
x11.XInitThreads()
|
||||||
|
# Explicitly set XOpenDisplay.restype to avoid segfault on 64 bit OS.
|
||||||
|
# http://stackoverflow.com/questions/35137007/get-mouse-position-on-linux-pure-python
|
||||||
|
x11.XOpenDisplay.restype = c_void_p
|
||||||
|
display = c_void_p(x11.XOpenDisplay(0))
|
||||||
|
window = x11.XDefaultRootWindow(display)
|
||||||
|
|
||||||
|
def get_position():
|
||||||
|
build_display()
|
||||||
|
root_id, child_id = c_void_p(), c_void_p()
|
||||||
|
root_x, root_y, win_x, win_y = c_int(), c_int(), c_int(), c_int()
|
||||||
|
mask = c_uint()
|
||||||
|
ret = x11.XQueryPointer(display, c_uint32(window), byref(root_id), byref(child_id),
|
||||||
|
byref(root_x), byref(root_y),
|
||||||
|
byref(win_x), byref(win_y), byref(mask))
|
||||||
|
return root_x.value, root_y.value
|
||||||
|
|
||||||
|
def move_to(x, y):
|
||||||
|
build_display()
|
||||||
|
x11.XWarpPointer(display, None, window, 0, 0, 0, 0, x, y)
|
||||||
|
x11.XFlush(display)
|
||||||
|
|
||||||
|
REL_X = 0x00
|
||||||
|
REL_Y = 0x01
|
||||||
|
REL_Z = 0x02
|
||||||
|
REL_HWHEEL = 0x06
|
||||||
|
REL_WHEEL = 0x08
|
||||||
|
|
||||||
|
ABS_X = 0x00
|
||||||
|
ABS_Y = 0x01
|
||||||
|
|
||||||
|
BTN_MOUSE = 0x110
|
||||||
|
BTN_LEFT = 0x110
|
||||||
|
BTN_RIGHT = 0x111
|
||||||
|
BTN_MIDDLE = 0x112
|
||||||
|
BTN_SIDE = 0x113
|
||||||
|
BTN_EXTRA = 0x114
|
||||||
|
|
||||||
|
button_by_code = {
|
||||||
|
BTN_LEFT: LEFT,
|
||||||
|
BTN_RIGHT: RIGHT,
|
||||||
|
BTN_MIDDLE: MIDDLE,
|
||||||
|
BTN_SIDE: X,
|
||||||
|
BTN_EXTRA: X2,
|
||||||
|
}
|
||||||
|
code_by_button = {button: code for code, button in button_by_code.items()}
|
||||||
|
|
||||||
|
device = None
|
||||||
|
def build_device():
|
||||||
|
global device
|
||||||
|
if device: return
|
||||||
|
ensure_root()
|
||||||
|
device = aggregate_devices('mouse')
|
||||||
|
init = build_device
|
||||||
|
|
||||||
|
def listen(queue):
|
||||||
|
build_device()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time, type, code, value, device_id = device.read_event()
|
||||||
|
if type == EV_SYN or type == EV_MSC:
|
||||||
|
continue
|
||||||
|
|
||||||
|
event = None
|
||||||
|
arg = None
|
||||||
|
|
||||||
|
if type == EV_KEY:
|
||||||
|
event = ButtonEvent(DOWN if value else UP, button_by_code.get(code, '?'), time)
|
||||||
|
elif type == EV_REL:
|
||||||
|
value, = struct.unpack('i', struct.pack('I', value))
|
||||||
|
|
||||||
|
if code == REL_WHEEL:
|
||||||
|
event = WheelEvent(value, time)
|
||||||
|
elif code in (REL_X, REL_Y):
|
||||||
|
x, y = get_position()
|
||||||
|
event = MoveEvent(x, y, time)
|
||||||
|
|
||||||
|
if event is None:
|
||||||
|
# Unknown event type.
|
||||||
|
continue
|
||||||
|
|
||||||
|
queue.put(event)
|
||||||
|
|
||||||
|
def press(button=LEFT):
|
||||||
|
build_device()
|
||||||
|
device.write_event(EV_KEY, code_by_button[button], 0x01)
|
||||||
|
|
||||||
|
def release(button=LEFT):
|
||||||
|
build_device()
|
||||||
|
device.write_event(EV_KEY, code_by_button[button], 0x00)
|
||||||
|
|
||||||
|
def move_relative(x, y):
|
||||||
|
build_device()
|
||||||
|
# Note relative events are not in terms of pixels, but millimeters.
|
||||||
|
if x < 0:
|
||||||
|
x += 2**32
|
||||||
|
if y < 0:
|
||||||
|
y += 2**32
|
||||||
|
device.write_event(EV_REL, REL_X, x)
|
||||||
|
device.write_event(EV_REL, REL_Y, y)
|
||||||
|
|
||||||
|
def wheel(delta=1):
|
||||||
|
build_device()
|
||||||
|
if delta < 0:
|
||||||
|
delta += 2**32
|
||||||
|
device.write_event(EV_REL, REL_WHEEL, delta)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
#listen(print)
|
||||||
|
move_to(100, 200)
|
@ -0,0 +1,216 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import ctypes
|
||||||
|
import time
|
||||||
|
from ctypes import c_short, c_char, c_uint8, c_int32, c_int, c_uint, c_uint32, c_long, byref, Structure, CFUNCTYPE, POINTER
|
||||||
|
from ctypes.wintypes import DWORD, BOOL, HHOOK, MSG, LPWSTR, WCHAR, WPARAM, LPARAM
|
||||||
|
LPMSG = POINTER(MSG)
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
|
||||||
|
from ._mouse_event import ButtonEvent, WheelEvent, MoveEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE, WHEEL, HORIZONTAL, VERTICAL
|
||||||
|
|
||||||
|
#user32 = ctypes.windll.user32
|
||||||
|
user32 = ctypes.WinDLL('user32', use_last_error = True)
|
||||||
|
|
||||||
|
class MSLLHOOKSTRUCT(Structure):
|
||||||
|
_fields_ = [("x", c_long),
|
||||||
|
("y", c_long),
|
||||||
|
('data', c_int32),
|
||||||
|
('reserved', c_int32),
|
||||||
|
("flags", DWORD),
|
||||||
|
("time", c_int),
|
||||||
|
]
|
||||||
|
|
||||||
|
LowLevelMouseProc = CFUNCTYPE(c_int, WPARAM, LPARAM, POINTER(MSLLHOOKSTRUCT))
|
||||||
|
|
||||||
|
SetWindowsHookEx = user32.SetWindowsHookExA
|
||||||
|
#SetWindowsHookEx.argtypes = [c_int, LowLevelMouseProc, c_int, c_int]
|
||||||
|
SetWindowsHookEx.restype = HHOOK
|
||||||
|
|
||||||
|
CallNextHookEx = user32.CallNextHookEx
|
||||||
|
#CallNextHookEx.argtypes = [c_int , c_int, c_int, POINTER(MSLLHOOKSTRUCT)]
|
||||||
|
CallNextHookEx.restype = c_int
|
||||||
|
|
||||||
|
UnhookWindowsHookEx = user32.UnhookWindowsHookEx
|
||||||
|
UnhookWindowsHookEx.argtypes = [HHOOK]
|
||||||
|
UnhookWindowsHookEx.restype = BOOL
|
||||||
|
|
||||||
|
GetMessage = user32.GetMessageW
|
||||||
|
GetMessage.argtypes = [LPMSG, c_int, c_int, c_int]
|
||||||
|
GetMessage.restype = BOOL
|
||||||
|
|
||||||
|
TranslateMessage = user32.TranslateMessage
|
||||||
|
TranslateMessage.argtypes = [LPMSG]
|
||||||
|
TranslateMessage.restype = BOOL
|
||||||
|
|
||||||
|
DispatchMessage = user32.DispatchMessageA
|
||||||
|
DispatchMessage.argtypes = [LPMSG]
|
||||||
|
|
||||||
|
GetDoubleClickTime = user32.GetDoubleClickTime
|
||||||
|
|
||||||
|
# Beware, as of 2016-01-30 the official docs have a very incomplete list.
|
||||||
|
# This one was compiled from experience and may be incomplete.
|
||||||
|
WM_MOUSEMOVE = 0x200
|
||||||
|
WM_LBUTTONDOWN = 0x201
|
||||||
|
WM_LBUTTONUP = 0x202
|
||||||
|
WM_LBUTTONDBLCLK = 0x203
|
||||||
|
WM_RBUTTONDOWN = 0x204
|
||||||
|
WM_RBUTTONUP = 0x205
|
||||||
|
WM_RBUTTONDBLCLK = 0x206
|
||||||
|
WM_MBUTTONDOWN = 0x207
|
||||||
|
WM_MBUTTONUP = 0x208
|
||||||
|
WM_MBUTTONDBLCLK = 0x209
|
||||||
|
WM_MOUSEWHEEL = 0x20A
|
||||||
|
WM_XBUTTONDOWN = 0x20B
|
||||||
|
WM_XBUTTONUP = 0x20C
|
||||||
|
WM_XBUTTONDBLCLK = 0x20D
|
||||||
|
WM_NCXBUTTONDOWN = 0x00AB
|
||||||
|
WM_NCXBUTTONUP = 0x00AC
|
||||||
|
WM_NCXBUTTONDBLCLK = 0x00AD
|
||||||
|
WM_MOUSEHWHEEL = 0x20E
|
||||||
|
WM_LBUTTONDOWN = 0x0201
|
||||||
|
WM_LBUTTONUP = 0x0202
|
||||||
|
WM_MOUSEMOVE = 0x0200
|
||||||
|
WM_MOUSEWHEEL = 0x020A
|
||||||
|
WM_MOUSEHWHEEL = 0x020E
|
||||||
|
WM_RBUTTONDOWN = 0x0204
|
||||||
|
WM_RBUTTONUP = 0x0205
|
||||||
|
|
||||||
|
buttons_by_wm_code = {
|
||||||
|
WM_LBUTTONDOWN: (DOWN, LEFT),
|
||||||
|
WM_LBUTTONUP: (UP, LEFT),
|
||||||
|
WM_LBUTTONDBLCLK: (DOUBLE, LEFT),
|
||||||
|
|
||||||
|
WM_RBUTTONDOWN: (DOWN, RIGHT),
|
||||||
|
WM_RBUTTONUP: (UP, RIGHT),
|
||||||
|
WM_RBUTTONDBLCLK: (DOUBLE, RIGHT),
|
||||||
|
|
||||||
|
WM_MBUTTONDOWN: (DOWN, MIDDLE),
|
||||||
|
WM_MBUTTONUP: (UP, MIDDLE),
|
||||||
|
WM_MBUTTONDBLCLK: (DOUBLE, MIDDLE),
|
||||||
|
|
||||||
|
WM_XBUTTONDOWN: (DOWN, X),
|
||||||
|
WM_XBUTTONUP: (UP, X),
|
||||||
|
WM_XBUTTONDBLCLK: (DOUBLE, X),
|
||||||
|
}
|
||||||
|
|
||||||
|
MOUSEEVENTF_ABSOLUTE = 0x8000
|
||||||
|
MOUSEEVENTF_MOVE = 0x1
|
||||||
|
MOUSEEVENTF_WHEEL = 0x800
|
||||||
|
MOUSEEVENTF_HWHEEL = 0x1000
|
||||||
|
MOUSEEVENTF_LEFTDOWN = 0x2
|
||||||
|
MOUSEEVENTF_LEFTUP = 0x4
|
||||||
|
MOUSEEVENTF_RIGHTDOWN = 0x8
|
||||||
|
MOUSEEVENTF_RIGHTUP = 0x10
|
||||||
|
MOUSEEVENTF_MIDDLEDOWN = 0x20
|
||||||
|
MOUSEEVENTF_MIDDLEUP = 0x40
|
||||||
|
MOUSEEVENTF_XDOWN = 0x0080
|
||||||
|
MOUSEEVENTF_XUP = 0x0100
|
||||||
|
|
||||||
|
simulated_mouse_codes = {
|
||||||
|
(WHEEL, HORIZONTAL): MOUSEEVENTF_HWHEEL,
|
||||||
|
(WHEEL, VERTICAL): MOUSEEVENTF_WHEEL,
|
||||||
|
|
||||||
|
(DOWN, LEFT): MOUSEEVENTF_LEFTDOWN,
|
||||||
|
(UP, LEFT): MOUSEEVENTF_LEFTUP,
|
||||||
|
|
||||||
|
(DOWN, RIGHT): MOUSEEVENTF_RIGHTDOWN,
|
||||||
|
(UP, RIGHT): MOUSEEVENTF_RIGHTUP,
|
||||||
|
|
||||||
|
(DOWN, MIDDLE): MOUSEEVENTF_MIDDLEDOWN,
|
||||||
|
(UP, MIDDLE): MOUSEEVENTF_MIDDLEUP,
|
||||||
|
|
||||||
|
(DOWN, X): MOUSEEVENTF_XDOWN,
|
||||||
|
(UP, X): MOUSEEVENTF_XUP,
|
||||||
|
}
|
||||||
|
|
||||||
|
NULL = c_int(0)
|
||||||
|
|
||||||
|
WHEEL_DELTA = 120
|
||||||
|
|
||||||
|
init = lambda: None
|
||||||
|
|
||||||
|
previous_button_event = None # defined in global scope
|
||||||
|
def listen(queue):
|
||||||
|
|
||||||
|
def low_level_mouse_handler(nCode, wParam, lParam):
|
||||||
|
global previous_button_event
|
||||||
|
|
||||||
|
struct = lParam.contents
|
||||||
|
# Can't use struct.time because it's usually zero.
|
||||||
|
t = time.time()
|
||||||
|
|
||||||
|
if wParam == WM_MOUSEMOVE:
|
||||||
|
event = MoveEvent(struct.x, struct.y, t)
|
||||||
|
elif wParam == WM_MOUSEWHEEL:
|
||||||
|
event = WheelEvent(struct.data / (WHEEL_DELTA * (2<<15)), t)
|
||||||
|
elif wParam in buttons_by_wm_code:
|
||||||
|
type, button = buttons_by_wm_code.get(wParam, ('?', '?'))
|
||||||
|
if wParam >= WM_XBUTTONDOWN:
|
||||||
|
button = {0x10000: X, 0x20000: X2}[struct.data]
|
||||||
|
event = ButtonEvent(type, button, t)
|
||||||
|
|
||||||
|
if (event.event_type == DOWN) and previous_button_event is not None:
|
||||||
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/gg153548%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
|
||||||
|
if event.time - previous_button_event.time <= GetDoubleClickTime() / 1000.0:
|
||||||
|
event = ButtonEvent(DOUBLE, event.button, event.time)
|
||||||
|
|
||||||
|
previous_button_event = event
|
||||||
|
else:
|
||||||
|
# Unknown event type.
|
||||||
|
return CallNextHookEx(NULL, nCode, wParam, lParam)
|
||||||
|
|
||||||
|
queue.put(event)
|
||||||
|
return CallNextHookEx(NULL, nCode, wParam, lParam)
|
||||||
|
|
||||||
|
WH_MOUSE_LL = c_int(14)
|
||||||
|
mouse_callback = LowLevelMouseProc(low_level_mouse_handler)
|
||||||
|
mouse_hook = SetWindowsHookEx(WH_MOUSE_LL, mouse_callback, NULL, NULL)
|
||||||
|
|
||||||
|
# Register to remove the hook when the interpreter exits. Unfortunately a
|
||||||
|
# try/finally block doesn't seem to work here.
|
||||||
|
atexit.register(UnhookWindowsHookEx, mouse_hook)
|
||||||
|
|
||||||
|
msg = LPMSG()
|
||||||
|
while not GetMessage(msg, NULL, NULL, NULL):
|
||||||
|
TranslateMessage(msg)
|
||||||
|
DispatchMessage(msg)
|
||||||
|
|
||||||
|
def _translate_button(button):
|
||||||
|
if button == X or button == X2:
|
||||||
|
return X, {X: 0x10000, X2: 0x20000}[button]
|
||||||
|
else:
|
||||||
|
return button, 0
|
||||||
|
|
||||||
|
def press(button=LEFT):
|
||||||
|
button, data = _translate_button(button)
|
||||||
|
code = simulated_mouse_codes[(DOWN, button)]
|
||||||
|
user32.mouse_event(code, 0, 0, data, 0)
|
||||||
|
|
||||||
|
def release(button=LEFT):
|
||||||
|
button, data = _translate_button(button)
|
||||||
|
code = simulated_mouse_codes[(UP, button)]
|
||||||
|
user32.mouse_event(code, 0, 0, data, 0)
|
||||||
|
|
||||||
|
def wheel(delta=1):
|
||||||
|
code = simulated_mouse_codes[(WHEEL, VERTICAL)]
|
||||||
|
user32.mouse_event(code, 0, 0, int(delta * WHEEL_DELTA), 0)
|
||||||
|
|
||||||
|
def move_to(x, y):
|
||||||
|
user32.SetCursorPos(int(x), int(y))
|
||||||
|
|
||||||
|
def move_relative(x, y):
|
||||||
|
user32.mouse_event(MOUSEEVENTF_MOVE, int(x), int(y), 0, 0)
|
||||||
|
|
||||||
|
class POINT(Structure):
|
||||||
|
_fields_ = [("x", c_long), ("y", c_long)]
|
||||||
|
|
||||||
|
def get_position():
|
||||||
|
point = POINT()
|
||||||
|
user32.GetCursorPos(byref(point))
|
||||||
|
return (point.x, point.y)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
def p(e):
|
||||||
|
print(e)
|
||||||
|
listen(p)
|
@ -0,0 +1 @@
|
|||||||
|
pip
|
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Lucas Boppre Niehues
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,62 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: mouse
|
||||||
|
Version: 0.7.1
|
||||||
|
Summary: Hook and simulate mouse events on Windows and Linux
|
||||||
|
Home-page: https://github.com/boppreh/mouse
|
||||||
|
Author: BoppreH
|
||||||
|
Author-email: boppreh@gmail.com
|
||||||
|
License: MIT
|
||||||
|
Keywords: mouse hook simulate hotkey
|
||||||
|
Platform: UNKNOWN
|
||||||
|
Classifier: Development Status :: 4 - Beta
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Operating System :: Microsoft :: Windows
|
||||||
|
Classifier: Operating System :: Unix
|
||||||
|
Classifier: Programming Language :: Python :: 2
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||||
|
Classifier: Topic :: Utilities
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
|
||||||
|
|
||||||
|
mouse
|
||||||
|
=====
|
||||||
|
|
||||||
|
Take full control of your mouse with this small Python library. Hook global events, register hotkeys, simulate mouse movement and clicks, and much more.
|
||||||
|
|
||||||
|
_Huge thanks to [Kirill Pavlov](http://kirillpavlov.com/) for donating the package name. If you are looking for the Cheddargetter.com client implementation, [`pip install mouse==0.5.0`](https://pypi.python.org/pypi/mouse/0.5.0)._
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Global event hook on all mice devices (captures events regardless of focus).
|
||||||
|
- **Listen** and **sends** mouse events.
|
||||||
|
- Works with **Windows** and **Linux** (requires sudo).
|
||||||
|
- **Pure Python**, no C modules to be compiled.
|
||||||
|
- **Zero dependencies**. Trivial to install and deploy, just copy the files.
|
||||||
|
- **Python 2 and 3**.
|
||||||
|
- Includes **high level API** (e.g. [record](#mouse.record) and [play](#mouse.play).
|
||||||
|
- Events automatically captured in separate thread, doesn't block main program.
|
||||||
|
- Tested and documented.
|
||||||
|
|
||||||
|
This program makes no attempt to hide itself, so don't use it for keyloggers.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Install the [PyPI package](https://pypi.python.org/pypi/mouse/):
|
||||||
|
|
||||||
|
$ sudo pip install mouse
|
||||||
|
|
||||||
|
or clone the repository (no installation required, source files are sufficient):
|
||||||
|
|
||||||
|
$ git clone https://github.com/boppreh/mouse
|
||||||
|
|
||||||
|
Then check the [API docs](https://github.com/boppreh/mouse#api) to see what features are available.
|
||||||
|
|
||||||
|
|
||||||
|
## Known limitations:
|
||||||
|
|
||||||
|
- Events generated under Windows don't report device id (`event.device == None`). [#21](https://github.com/boppreh/keyboard/issues/21)
|
||||||
|
- To avoid depending on X the Linux parts reads raw device files (`/dev/input/input*`) but this requries root.
|
||||||
|
- Other applications, such as some games, may register hooks that swallow all key events. In this case `mouse` will be unable to report events.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
mouse-0.7.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
mouse-0.7.1.dist-info/LICENSE.txt,sha256=K_FKUlVV0FqAHC0sKJAVBPt2q-58LX6_tM_HUI198Pc,1077
|
||||||
|
mouse-0.7.1.dist-info/METADATA,sha256=7MmeikMZ3xgUFvcSNMnCnXugXuG0ag8QjFoOV1k9mr0,2455
|
||||||
|
mouse-0.7.1.dist-info/RECORD,,
|
||||||
|
mouse-0.7.1.dist-info/WHEEL,sha256=JZXtYepZFsf4IoivNpnSgKIc4qTHan08DRd56koo_DM,116
|
||||||
|
mouse-0.7.1.dist-info/top_level.txt,sha256=FiA9sXRt9_B8X6je4BlPesL9EakrmGZ0fDKPK09Ql6U,6
|
||||||
|
mouse/__init__.py,sha256=HGaiOxH8PuKnU2oCLN5pEN4yuvxH1JoxT1QXwITFlf0,9147
|
||||||
|
mouse/__main__.py,sha256=Rir1qIanuA6OUsVIywo71MgF1PrVCdZ77F1FPOu4JA0,625
|
||||||
|
mouse/__pycache__/__init__.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/__main__.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_generic.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_mouse_event.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_mouse_tests.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_nixcommon.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_nixmouse.cpython-37.pyc,,
|
||||||
|
mouse/__pycache__/_winmouse.cpython-37.pyc,,
|
||||||
|
mouse/_generic.py,sha256=STzfL7AUAkcAq6XUyxpQiOYnBnQ1TZjWL1cSFSZ61_o,2132
|
||||||
|
mouse/_mouse_event.py,sha256=zRQGO6M6nbA-jvhI0yz4HzvQNcScF48PZ9iRegcTVjQ,422
|
||||||
|
mouse/_mouse_tests.py,sha256=gNa_NW5sRIsbLMG35ZfXal3eHS2mEpEPhysJRDq0gQA,10000
|
||||||
|
mouse/_nixcommon.py,sha256=FNXiCv7u_A0SzcYJQlYDJBSdIN6IUTVLG4g7oqXgj6o,5552
|
||||||
|
mouse/_nixmouse.py,sha256=3htKn9XkUApFqp3rGCT-ZQIqJFf4iGqoxxoO9MY2x4k,3576
|
||||||
|
mouse/_winmouse.py,sha256=lWAnfGq0etNjFR-vE9e3r9N7Amg5wU957B4ZS3YMTz0,6449
|
@ -0,0 +1,6 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.33.6)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py2-none-any
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
mouse
|
@ -0,0 +1,272 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
mouse
|
||||||
|
=====
|
||||||
|
|
||||||
|
Take full control of your mouse with this small Python library. Hook global events, register hotkeys, simulate mouse movement and clicks, and much more.
|
||||||
|
|
||||||
|
_Huge thanks to [Kirill Pavlov](http://kirillpavlov.com/) for donating the package name. If you are looking for the Cheddargetter.com client implementation, [`pip install mouse==0.5.0`](https://pypi.python.org/pypi/mouse/0.5.0)._
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Global event hook on all mice devices (captures events regardless of focus).
|
||||||
|
- **Listen** and **sends** mouse events.
|
||||||
|
- Works with **Windows** and **Linux** (requires sudo).
|
||||||
|
- **Pure Python**, no C modules to be compiled.
|
||||||
|
- **Zero dependencies**. Trivial to install and deploy, just copy the files.
|
||||||
|
- **Python 2 and 3**.
|
||||||
|
- Includes **high level API** (e.g. [record](#mouse.record) and [play](#mouse.play).
|
||||||
|
- Events automatically captured in separate thread, doesn't block main program.
|
||||||
|
- Tested and documented.
|
||||||
|
|
||||||
|
This program makes no attempt to hide itself, so don't use it for keyloggers.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Install the [PyPI package](https://pypi.python.org/pypi/mouse/):
|
||||||
|
|
||||||
|
$ sudo pip install mouse
|
||||||
|
|
||||||
|
or clone the repository (no installation required, source files are sufficient):
|
||||||
|
|
||||||
|
$ git clone https://github.com/boppreh/mouse
|
||||||
|
|
||||||
|
Then check the [API docs](https://github.com/boppreh/mouse#api) to see what features are available.
|
||||||
|
|
||||||
|
|
||||||
|
## Known limitations:
|
||||||
|
|
||||||
|
- Events generated under Windows don't report device id (`event.device == None`). [#21](https://github.com/boppreh/keyboard/issues/21)
|
||||||
|
- To avoid depending on X the Linux parts reads raw device files (`/dev/input/input*`) but this requries root.
|
||||||
|
- Other applications, such as some games, may register hooks that swallow all key events. In this case `mouse` will be unable to report events.
|
||||||
|
"""
|
||||||
|
# TODO
|
||||||
|
# - infinite wait
|
||||||
|
# - mouse.on_move
|
||||||
|
version = '0.7.1'
|
||||||
|
|
||||||
|
import time as _time
|
||||||
|
|
||||||
|
import platform as _platform
|
||||||
|
if _platform.system() == 'Windows':
|
||||||
|
from. import _winmouse as _os_mouse
|
||||||
|
elif _platform.system() == 'Linux':
|
||||||
|
from. import _nixmouse as _os_mouse
|
||||||
|
else:
|
||||||
|
raise OSError("Unsupported platform '{}'".format(_platform.system()))
|
||||||
|
|
||||||
|
from ._mouse_event import ButtonEvent, MoveEvent, WheelEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE
|
||||||
|
from ._generic import GenericListener as _GenericListener
|
||||||
|
|
||||||
|
_pressed_events = set()
|
||||||
|
class _MouseListener(_GenericListener):
|
||||||
|
def init(self):
|
||||||
|
_os_mouse.init()
|
||||||
|
def pre_process_event(self, event):
|
||||||
|
if isinstance(event, ButtonEvent):
|
||||||
|
if event.event_type in (UP, DOUBLE):
|
||||||
|
_pressed_events.discard(event.button)
|
||||||
|
else:
|
||||||
|
_pressed_events.add(event.button)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def listen(self):
|
||||||
|
_os_mouse.listen(self.queue)
|
||||||
|
|
||||||
|
_listener = _MouseListener()
|
||||||
|
|
||||||
|
def is_pressed(button=LEFT):
|
||||||
|
""" Returns True if the given button is currently pressed. """
|
||||||
|
_listener.start_if_necessary()
|
||||||
|
return button in _pressed_events
|
||||||
|
|
||||||
|
def press(button=LEFT):
|
||||||
|
""" Presses the given button (but doesn't release). """
|
||||||
|
_os_mouse.press(button)
|
||||||
|
|
||||||
|
def release(button=LEFT):
|
||||||
|
""" Releases the given button. """
|
||||||
|
_os_mouse.release(button)
|
||||||
|
|
||||||
|
def click(button=LEFT):
|
||||||
|
""" Sends a click with the given button. """
|
||||||
|
_os_mouse.press(button)
|
||||||
|
_os_mouse.release(button)
|
||||||
|
|
||||||
|
def double_click(button=LEFT):
|
||||||
|
""" Sends a double click with the given button. """
|
||||||
|
click(button)
|
||||||
|
click(button)
|
||||||
|
|
||||||
|
def right_click():
|
||||||
|
""" Sends a right click with the given button. """
|
||||||
|
click(RIGHT)
|
||||||
|
|
||||||
|
def wheel(delta=1):
|
||||||
|
""" Scrolls the wheel `delta` clicks. Sign indicates direction. """
|
||||||
|
_os_mouse.wheel(delta)
|
||||||
|
|
||||||
|
def move(x, y, absolute=True, duration=0):
|
||||||
|
"""
|
||||||
|
Moves the mouse. If `absolute`, to position (x, y), otherwise move relative
|
||||||
|
to the current position. If `duration` is non-zero, animates the movement.
|
||||||
|
"""
|
||||||
|
x = int(x)
|
||||||
|
y = int(y)
|
||||||
|
|
||||||
|
# Requires an extra system call on Linux, but `move_relative` is measured
|
||||||
|
# in millimiters so we would lose precision.
|
||||||
|
position_x, position_y = get_position()
|
||||||
|
|
||||||
|
if not absolute:
|
||||||
|
x = position_x + x
|
||||||
|
y = position_y + y
|
||||||
|
|
||||||
|
if duration:
|
||||||
|
start_x = position_x
|
||||||
|
start_y = position_y
|
||||||
|
dx = x - start_x
|
||||||
|
dy = y - start_y
|
||||||
|
|
||||||
|
if dx == 0 and dy == 0:
|
||||||
|
_time.sleep(duration)
|
||||||
|
else:
|
||||||
|
# 120 movements per second.
|
||||||
|
# Round and keep float to ensure float division in Python 2
|
||||||
|
steps = max(1.0, float(int(duration * 120.0)))
|
||||||
|
for i in range(int(steps)+1):
|
||||||
|
move(start_x + dx*i/steps, start_y + dy*i/steps)
|
||||||
|
_time.sleep(duration/steps)
|
||||||
|
else:
|
||||||
|
_os_mouse.move_to(x, y)
|
||||||
|
|
||||||
|
def drag(start_x, start_y, end_x, end_y, absolute=True, duration=0):
|
||||||
|
"""
|
||||||
|
Holds the left mouse button, moving from start to end position, then
|
||||||
|
releases. `absolute` and `duration` are parameters regarding the mouse
|
||||||
|
movement.
|
||||||
|
"""
|
||||||
|
if is_pressed():
|
||||||
|
release()
|
||||||
|
move(start_x, start_y, absolute, 0)
|
||||||
|
press()
|
||||||
|
move(end_x, end_y, absolute, duration)
|
||||||
|
release()
|
||||||
|
|
||||||
|
def on_button(callback, args=(), buttons=(LEFT, MIDDLE, RIGHT, X, X2), types=(UP, DOWN, DOUBLE)):
|
||||||
|
""" Invokes `callback` with `args` when the specified event happens. """
|
||||||
|
if not isinstance(buttons, (tuple, list)):
|
||||||
|
buttons = (buttons,)
|
||||||
|
if not isinstance(types, (tuple, list)):
|
||||||
|
types = (types,)
|
||||||
|
|
||||||
|
def handler(event):
|
||||||
|
if isinstance(event, ButtonEvent):
|
||||||
|
if event.event_type in types and event.button in buttons:
|
||||||
|
callback(*args)
|
||||||
|
_listener.add_handler(handler)
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def on_click(callback, args=()):
|
||||||
|
""" Invokes `callback` with `args` when the left button is clicked. """
|
||||||
|
return on_button(callback, args, [LEFT], [UP])
|
||||||
|
|
||||||
|
def on_double_click(callback, args=()):
|
||||||
|
"""
|
||||||
|
Invokes `callback` with `args` when the left button is double clicked.
|
||||||
|
"""
|
||||||
|
return on_button(callback, args, [LEFT], [DOUBLE])
|
||||||
|
|
||||||
|
def on_right_click(callback, args=()):
|
||||||
|
""" Invokes `callback` with `args` when the right button is clicked. """
|
||||||
|
return on_button(callback, args, [RIGHT], [UP])
|
||||||
|
|
||||||
|
def on_middle_click(callback, args=()):
|
||||||
|
""" Invokes `callback` with `args` when the middle button is clicked. """
|
||||||
|
return on_button(callback, args, [MIDDLE], [UP])
|
||||||
|
|
||||||
|
def wait(button=LEFT, target_types=(UP, DOWN, DOUBLE)):
|
||||||
|
"""
|
||||||
|
Blocks program execution until the given button performs an event.
|
||||||
|
"""
|
||||||
|
from threading import Lock
|
||||||
|
lock = Lock()
|
||||||
|
lock.acquire()
|
||||||
|
handler = on_button(lock.release, (), [button], target_types)
|
||||||
|
lock.acquire()
|
||||||
|
_listener.remove_handler(handler)
|
||||||
|
|
||||||
|
def get_position():
|
||||||
|
""" Returns the (x, y) mouse position. """
|
||||||
|
return _os_mouse.get_position()
|
||||||
|
|
||||||
|
def hook(callback):
|
||||||
|
"""
|
||||||
|
Installs a global listener on all available mouses, invoking `callback`
|
||||||
|
each time it is moved, a key status changes or the wheel is spun. A mouse
|
||||||
|
event is passed as argument, with type either `mouse.ButtonEvent`,
|
||||||
|
`mouse.WheelEvent` or `mouse.MoveEvent`.
|
||||||
|
|
||||||
|
Returns the given callback for easier development.
|
||||||
|
"""
|
||||||
|
_listener.add_handler(callback)
|
||||||
|
return callback
|
||||||
|
|
||||||
|
def unhook(callback):
|
||||||
|
"""
|
||||||
|
Removes a previously installed hook.
|
||||||
|
"""
|
||||||
|
_listener.remove_handler(callback)
|
||||||
|
|
||||||
|
def unhook_all():
|
||||||
|
"""
|
||||||
|
Removes all hooks registered by this application. Note this may include
|
||||||
|
hooks installed by high level functions, such as `record`.
|
||||||
|
"""
|
||||||
|
del _listener.handlers[:]
|
||||||
|
|
||||||
|
def record(button=RIGHT, target_types=(DOWN,)):
|
||||||
|
"""
|
||||||
|
Records all mouse events until the user presses the given button.
|
||||||
|
Then returns the list of events recorded. Pairs well with `play(events)`.
|
||||||
|
|
||||||
|
Note: this is a blocking function.
|
||||||
|
Note: for more details on the mouse hook and events see `hook`.
|
||||||
|
"""
|
||||||
|
recorded = []
|
||||||
|
hook(recorded.append)
|
||||||
|
wait(button=button, target_types=target_types)
|
||||||
|
unhook(recorded.append)
|
||||||
|
return recorded
|
||||||
|
|
||||||
|
def play(events, speed_factor=1.0, include_clicks=True, include_moves=True, include_wheel=True):
|
||||||
|
"""
|
||||||
|
Plays a sequence of recorded events, maintaining the relative time
|
||||||
|
intervals. If speed_factor is <= 0 then the actions are replayed as fast
|
||||||
|
as the OS allows. Pairs well with `record()`.
|
||||||
|
|
||||||
|
The parameters `include_*` define if events of that type should be inluded
|
||||||
|
in the replay or ignored.
|
||||||
|
"""
|
||||||
|
last_time = None
|
||||||
|
for event in events:
|
||||||
|
if speed_factor > 0 and last_time is not None:
|
||||||
|
_time.sleep((event.time - last_time) / speed_factor)
|
||||||
|
last_time = event.time
|
||||||
|
|
||||||
|
if isinstance(event, ButtonEvent) and include_clicks:
|
||||||
|
if event.event_type == UP:
|
||||||
|
_os_mouse.release(event.button)
|
||||||
|
else:
|
||||||
|
_os_mouse.press(event.button)
|
||||||
|
elif isinstance(event, MoveEvent) and include_moves:
|
||||||
|
_os_mouse.move_to(event.x, event.y)
|
||||||
|
elif isinstance(event, WheelEvent) and include_wheel:
|
||||||
|
_os_mouse.wheel(event.delta)
|
||||||
|
|
||||||
|
replay = play
|
||||||
|
hold = press
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('Recording... Double click to stop and replay.')
|
||||||
|
play(record())
|
@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import mouse
|
||||||
|
import fileinput
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class_by_name = {
|
||||||
|
'ButtonEvent': mouse.ButtonEvent,
|
||||||
|
'WheelEvent': mouse.WheelEvent,
|
||||||
|
'MoveEvent': mouse.MoveEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_event_json(event):
|
||||||
|
# Could use json.dumps(event.__dict__()), but this way we guarantee semantic order.
|
||||||
|
d = event._asdict()
|
||||||
|
d['event_class'] = event.__class__.__name__
|
||||||
|
print(json.dumps(d))
|
||||||
|
sys.stdout.flush()
|
||||||
|
mouse.hook(print_event_json)
|
||||||
|
|
||||||
|
def load(line):
|
||||||
|
d = json.loads(line)
|
||||||
|
class_ = class_by_name[d['event_class']]
|
||||||
|
del d['event_class']
|
||||||
|
return class_(**d)
|
||||||
|
|
||||||
|
mouse.play(load(line) for line in fileinput.input())
|
@ -0,0 +1,72 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from threading import Thread, Lock
|
||||||
|
import traceback
|
||||||
|
import functools
|
||||||
|
|
||||||
|
try:
|
||||||
|
from queue import Queue
|
||||||
|
except ImportError:
|
||||||
|
from Queue import Queue
|
||||||
|
|
||||||
|
class GenericListener(object):
|
||||||
|
lock = Lock()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.handlers = []
|
||||||
|
self.listening = False
|
||||||
|
self.queue = Queue()
|
||||||
|
|
||||||
|
def invoke_handlers(self, event):
|
||||||
|
for handler in self.handlers:
|
||||||
|
try:
|
||||||
|
if handler(event):
|
||||||
|
# Stop processing this hotkey.
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def start_if_necessary(self):
|
||||||
|
"""
|
||||||
|
Starts the listening thread if it wasn't already.
|
||||||
|
"""
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
if not self.listening:
|
||||||
|
self.init()
|
||||||
|
|
||||||
|
self.listening = True
|
||||||
|
self.listening_thread = Thread(target=self.listen)
|
||||||
|
self.listening_thread.daemon = True
|
||||||
|
self.listening_thread.start()
|
||||||
|
|
||||||
|
self.processing_thread = Thread(target=self.process)
|
||||||
|
self.processing_thread.daemon = True
|
||||||
|
self.processing_thread.start()
|
||||||
|
finally:
|
||||||
|
self.lock.release()
|
||||||
|
|
||||||
|
def pre_process_event(self, event):
|
||||||
|
raise NotImplementedError('This method should be implemented in the child class.')
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
"""
|
||||||
|
Loops over the underlying queue of events and processes them in order.
|
||||||
|
"""
|
||||||
|
assert self.queue is not None
|
||||||
|
while True:
|
||||||
|
event = self.queue.get()
|
||||||
|
if self.pre_process_event(event):
|
||||||
|
self.invoke_handlers(event)
|
||||||
|
self.queue.task_done()
|
||||||
|
|
||||||
|
def add_handler(self, handler):
|
||||||
|
"""
|
||||||
|
Adds a function to receive each event captured, starting the capturing
|
||||||
|
process if necessary.
|
||||||
|
"""
|
||||||
|
self.start_if_necessary()
|
||||||
|
self.handlers.append(handler)
|
||||||
|
|
||||||
|
def remove_handler(self, handler):
|
||||||
|
""" Removes a previously added event handler. """
|
||||||
|
self.handlers.remove(handler)
|
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
LEFT = 'left'
|
||||||
|
RIGHT = 'right'
|
||||||
|
MIDDLE = 'middle'
|
||||||
|
WHEEL = 'wheel'
|
||||||
|
X = 'x'
|
||||||
|
X2 = 'x2'
|
||||||
|
|
||||||
|
UP = 'up'
|
||||||
|
DOWN = 'down'
|
||||||
|
DOUBLE = 'double'
|
||||||
|
VERTICAL = 'vertical'
|
||||||
|
HORIZONTAL = 'horizontal'
|
||||||
|
|
||||||
|
|
||||||
|
ButtonEvent = namedtuple('ButtonEvent', ['event_type', 'button', 'time'])
|
||||||
|
WheelEvent = namedtuple('WheelEvent', ['delta', 'time'])
|
||||||
|
MoveEvent = namedtuple('MoveEvent', ['x', 'y', 'time'])
|
@ -0,0 +1,271 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import unittest
|
||||||
|
import time
|
||||||
|
|
||||||
|
from ._mouse_event import MoveEvent, ButtonEvent, WheelEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE
|
||||||
|
import mouse
|
||||||
|
|
||||||
|
class FakeOsMouse(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.append = None
|
||||||
|
self.position = (0, 0)
|
||||||
|
self.queue = None
|
||||||
|
self.init = lambda: None
|
||||||
|
|
||||||
|
def listen(self, queue):
|
||||||
|
self.listening = True
|
||||||
|
self.queue = queue
|
||||||
|
|
||||||
|
def press(self, button):
|
||||||
|
self.append((DOWN, button))
|
||||||
|
|
||||||
|
def release(self, button):
|
||||||
|
self.append((UP, button))
|
||||||
|
|
||||||
|
def get_position(self):
|
||||||
|
return self.position
|
||||||
|
|
||||||
|
def move_to(self, x, y):
|
||||||
|
self.append(('move', (x, y)))
|
||||||
|
self.position = (x, y)
|
||||||
|
|
||||||
|
def wheel(self, delta):
|
||||||
|
self.append(('wheel', delta))
|
||||||
|
|
||||||
|
def move_relative(self, x, y):
|
||||||
|
self.position = (self.position[0] + x, self.position[1] + y)
|
||||||
|
|
||||||
|
class TestMouse(unittest.TestCase):
|
||||||
|
@staticmethod
|
||||||
|
def setUpClass():
|
||||||
|
mouse._os_mouse= FakeOsMouse()
|
||||||
|
mouse._listener.start_if_necessary()
|
||||||
|
assert mouse._os_mouse.listening
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.events = []
|
||||||
|
mouse._pressed_events.clear()
|
||||||
|
mouse._os_mouse.append = self.events.append
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
mouse.unhook_all()
|
||||||
|
# Make sure there's no spill over between tests.
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def wait_for_events_queue(self):
|
||||||
|
mouse._listener.queue.join()
|
||||||
|
|
||||||
|
def flush_events(self):
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
events = list(self.events)
|
||||||
|
# Ugly, but requried to work in Python2. Python3 has list.clear
|
||||||
|
del self.events[:]
|
||||||
|
return events
|
||||||
|
|
||||||
|
def press(self, button=LEFT):
|
||||||
|
mouse._os_mouse.queue.put(ButtonEvent(DOWN, button, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def release(self, button=LEFT):
|
||||||
|
mouse._os_mouse.queue.put(ButtonEvent(UP, button, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def double_click(self, button=LEFT):
|
||||||
|
mouse._os_mouse.queue.put(ButtonEvent(DOUBLE, button, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def click(self, button=LEFT):
|
||||||
|
self.press(button)
|
||||||
|
self.release(button)
|
||||||
|
|
||||||
|
def wheel(self, delta=1):
|
||||||
|
mouse._os_mouse.queue.put(WheelEvent(delta, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def move(self, x=0, y=0):
|
||||||
|
mouse._os_mouse.queue.put(MoveEvent(x, y, time.time()))
|
||||||
|
self.wait_for_events_queue()
|
||||||
|
|
||||||
|
def test_hook(self):
|
||||||
|
events = []
|
||||||
|
self.press()
|
||||||
|
mouse.hook(events.append)
|
||||||
|
self.press()
|
||||||
|
mouse.unhook(events.append)
|
||||||
|
self.press()
|
||||||
|
self.assertEqual(len(events), 1)
|
||||||
|
|
||||||
|
def test_is_pressed(self):
|
||||||
|
self.assertFalse(mouse.is_pressed())
|
||||||
|
self.press()
|
||||||
|
self.assertTrue(mouse.is_pressed())
|
||||||
|
self.release()
|
||||||
|
self.press(X2)
|
||||||
|
self.assertFalse(mouse.is_pressed())
|
||||||
|
|
||||||
|
self.assertTrue(mouse.is_pressed(X2))
|
||||||
|
self.press(X2)
|
||||||
|
self.assertTrue(mouse.is_pressed(X2))
|
||||||
|
self.release(X2)
|
||||||
|
self.release(X2)
|
||||||
|
self.assertFalse(mouse.is_pressed(X2))
|
||||||
|
|
||||||
|
def test_buttons(self):
|
||||||
|
mouse.press()
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, LEFT)])
|
||||||
|
mouse.release()
|
||||||
|
self.assertEqual(self.flush_events(), [(UP, LEFT)])
|
||||||
|
mouse.click()
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, LEFT), (UP, LEFT)])
|
||||||
|
mouse.double_click()
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, LEFT), (UP, LEFT), (DOWN, LEFT), (UP, LEFT)])
|
||||||
|
mouse.right_click()
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, RIGHT), (UP, RIGHT)])
|
||||||
|
mouse.click(RIGHT)
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, RIGHT), (UP, RIGHT)])
|
||||||
|
mouse.press(X2)
|
||||||
|
self.assertEqual(self.flush_events(), [(DOWN, X2)])
|
||||||
|
|
||||||
|
def test_position(self):
|
||||||
|
self.assertEqual(mouse.get_position(), mouse._os_mouse.get_position())
|
||||||
|
|
||||||
|
def test_move(self):
|
||||||
|
mouse.move(0, 0)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (0, 0))
|
||||||
|
mouse.move(100, 500)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (100, 500))
|
||||||
|
mouse.move(1, 2, False)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (101, 502))
|
||||||
|
|
||||||
|
mouse.move(0, 0)
|
||||||
|
mouse.move(100, 499, True, duration=0.01)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (100, 499))
|
||||||
|
mouse.move(100, 1, False, duration=0.01)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (200, 500))
|
||||||
|
mouse.move(0, 0, False, duration=0.01)
|
||||||
|
self.assertEqual(mouse._os_mouse.get_position(), (200, 500))
|
||||||
|
|
||||||
|
def triggers(self, fn, events, **kwargs):
|
||||||
|
self.triggered = False
|
||||||
|
def callback():
|
||||||
|
self.triggered = True
|
||||||
|
handler = fn(callback, **kwargs)
|
||||||
|
|
||||||
|
for event_type, arg in events:
|
||||||
|
if event_type == DOWN:
|
||||||
|
self.press(arg)
|
||||||
|
elif event_type == UP:
|
||||||
|
self.release(arg)
|
||||||
|
elif event_type == DOUBLE:
|
||||||
|
self.double_click(arg)
|
||||||
|
elif event_type == 'WHEEL':
|
||||||
|
self.wheel()
|
||||||
|
|
||||||
|
mouse._listener.remove_handler(handler)
|
||||||
|
return self.triggered
|
||||||
|
|
||||||
|
def test_on_button(self):
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, LEFT)]))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, RIGHT)]))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, X)]))
|
||||||
|
|
||||||
|
self.assertFalse(self.triggers(mouse.on_button, [('WHEEL', '')]))
|
||||||
|
|
||||||
|
self.assertFalse(self.triggers(mouse.on_button, [(DOWN, X)], buttons=MIDDLE))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE, types=UP))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(UP, MIDDLE)], buttons=MIDDLE, types=UP))
|
||||||
|
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(UP, MIDDLE)], buttons=[MIDDLE, LEFT], types=[UP, DOWN]))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_button, [(DOWN, LEFT)], buttons=[MIDDLE, LEFT], types=[UP, DOWN]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_button, [(UP, X)], buttons=[MIDDLE, LEFT], types=[UP, DOWN]))
|
||||||
|
|
||||||
|
def test_ons(self):
|
||||||
|
self.assertTrue(self.triggers(mouse.on_click, [(UP, LEFT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_click, [(UP, RIGHT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_click, [(DOWN, LEFT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_click, [(DOWN, RIGHT)]))
|
||||||
|
|
||||||
|
self.assertTrue(self.triggers(mouse.on_double_click, [(DOUBLE, LEFT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_double_click, [(DOUBLE, RIGHT)]))
|
||||||
|
self.assertFalse(self.triggers(mouse.on_double_click, [(DOWN, RIGHT)]))
|
||||||
|
|
||||||
|
self.assertTrue(self.triggers(mouse.on_right_click, [(UP, RIGHT)]))
|
||||||
|
self.assertTrue(self.triggers(mouse.on_middle_click, [(UP, MIDDLE)]))
|
||||||
|
|
||||||
|
def test_wait(self):
|
||||||
|
# If this fails it blocks. Unfortunately, but I see no other way of testing.
|
||||||
|
from threading import Thread, Lock
|
||||||
|
lock = Lock()
|
||||||
|
lock.acquire()
|
||||||
|
def t():
|
||||||
|
mouse.wait()
|
||||||
|
lock.release()
|
||||||
|
Thread(target=t).start()
|
||||||
|
self.press()
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
def test_record_play(self):
|
||||||
|
from threading import Thread, Lock
|
||||||
|
lock = Lock()
|
||||||
|
lock.acquire()
|
||||||
|
def t():
|
||||||
|
self.recorded = mouse.record(RIGHT)
|
||||||
|
lock.release()
|
||||||
|
Thread(target=t).start()
|
||||||
|
self.click()
|
||||||
|
self.wheel(5)
|
||||||
|
self.move(100, 50)
|
||||||
|
self.press(RIGHT)
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
self.assertEqual(len(self.recorded), 5)
|
||||||
|
self.assertEqual(self.recorded[0]._replace(time=None), ButtonEvent(DOWN, LEFT, None))
|
||||||
|
self.assertEqual(self.recorded[1]._replace(time=None), ButtonEvent(UP, LEFT, None))
|
||||||
|
self.assertEqual(self.recorded[2]._replace(time=None), WheelEvent(5, None))
|
||||||
|
self.assertEqual(self.recorded[3]._replace(time=None), MoveEvent(100, 50, None))
|
||||||
|
self.assertEqual(self.recorded[4]._replace(time=None), ButtonEvent(DOWN, RIGHT, None))
|
||||||
|
|
||||||
|
mouse.play(self.recorded, speed_factor=0)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 5)
|
||||||
|
self.assertEqual(events[0], (DOWN, LEFT))
|
||||||
|
self.assertEqual(events[1], (UP, LEFT))
|
||||||
|
self.assertEqual(events[2], ('wheel', 5))
|
||||||
|
self.assertEqual(events[3], ('move', (100, 50)))
|
||||||
|
self.assertEqual(events[4], (DOWN, RIGHT))
|
||||||
|
|
||||||
|
mouse.play(self.recorded)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 5)
|
||||||
|
self.assertEqual(events[0], (DOWN, LEFT))
|
||||||
|
self.assertEqual(events[1], (UP, LEFT))
|
||||||
|
self.assertEqual(events[2], ('wheel', 5))
|
||||||
|
self.assertEqual(events[3], ('move', (100, 50)))
|
||||||
|
self.assertEqual(events[4], (DOWN, RIGHT))
|
||||||
|
|
||||||
|
mouse.play(self.recorded, include_clicks=False)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 2)
|
||||||
|
self.assertEqual(events[0], ('wheel', 5))
|
||||||
|
self.assertEqual(events[1], ('move', (100, 50)))
|
||||||
|
|
||||||
|
mouse.play(self.recorded, include_moves=False)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 4)
|
||||||
|
self.assertEqual(events[0], (DOWN, LEFT))
|
||||||
|
self.assertEqual(events[1], (UP, LEFT))
|
||||||
|
self.assertEqual(events[2], ('wheel', 5))
|
||||||
|
self.assertEqual(events[3], (DOWN, RIGHT))
|
||||||
|
|
||||||
|
mouse.play(self.recorded, include_wheel=False)
|
||||||
|
events = self.flush_events()
|
||||||
|
self.assertEqual(len(events), 4)
|
||||||
|
self.assertEqual(events[0], (DOWN, LEFT))
|
||||||
|
self.assertEqual(events[1], (UP, LEFT))
|
||||||
|
self.assertEqual(events[2], ('move', (100, 50)))
|
||||||
|
self.assertEqual(events[3], (DOWN, RIGHT))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -0,0 +1,165 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import struct
|
||||||
|
import os
|
||||||
|
import atexit
|
||||||
|
from time import time as now
|
||||||
|
from threading import Thread
|
||||||
|
from glob import glob
|
||||||
|
try:
|
||||||
|
from queue import Queue
|
||||||
|
except ImportError:
|
||||||
|
from Queue import Queue
|
||||||
|
|
||||||
|
event_bin_format = 'llHHI'
|
||||||
|
|
||||||
|
# Taken from include/linux/input.h
|
||||||
|
# https://www.kernel.org/doc/Documentation/input/event-codes.txt
|
||||||
|
EV_SYN = 0x00
|
||||||
|
EV_KEY = 0x01
|
||||||
|
EV_REL = 0x02
|
||||||
|
EV_ABS = 0x03
|
||||||
|
EV_MSC = 0x04
|
||||||
|
|
||||||
|
def make_uinput():
|
||||||
|
import fcntl, struct
|
||||||
|
|
||||||
|
# Requires uinput driver, but it's usually available.
|
||||||
|
uinput = open("/dev/uinput", 'wb')
|
||||||
|
UI_SET_EVBIT = 0x40045564
|
||||||
|
fcntl.ioctl(uinput, UI_SET_EVBIT, EV_KEY)
|
||||||
|
|
||||||
|
UI_SET_KEYBIT = 0x40045565
|
||||||
|
for i in range(256):
|
||||||
|
fcntl.ioctl(uinput, UI_SET_KEYBIT, i)
|
||||||
|
|
||||||
|
BUS_USB = 0x03
|
||||||
|
uinput_user_dev = "80sHHHHi64i64i64i64i"
|
||||||
|
axis = [0] * 64 * 4
|
||||||
|
uinput.write(struct.pack(uinput_user_dev, b"Virtual Keyboard", BUS_USB, 1, 1, 1, 0, *axis))
|
||||||
|
uinput.flush() # Without this you may get Errno 22: Invalid argument.
|
||||||
|
|
||||||
|
UI_DEV_CREATE = 0x5501
|
||||||
|
fcntl.ioctl(uinput, UI_DEV_CREATE)
|
||||||
|
UI_DEV_DESTROY = 0x5502
|
||||||
|
#fcntl.ioctl(uinput, UI_DEV_DESTROY)
|
||||||
|
|
||||||
|
return uinput
|
||||||
|
|
||||||
|
class EventDevice(object):
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
self._input_file = None
|
||||||
|
self._output_file = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def input_file(self):
|
||||||
|
if self._input_file is None:
|
||||||
|
try:
|
||||||
|
self._input_file = open(self.path, 'rb')
|
||||||
|
except IOError as e:
|
||||||
|
if e.strerror == 'Permission denied':
|
||||||
|
print('Permission denied ({}). You must be sudo to access global events.'.format(self.path))
|
||||||
|
exit()
|
||||||
|
|
||||||
|
def try_close():
|
||||||
|
try:
|
||||||
|
self._input_file.close
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
atexit.register(try_close)
|
||||||
|
return self._input_file
|
||||||
|
|
||||||
|
@property
|
||||||
|
def output_file(self):
|
||||||
|
if self._output_file is None:
|
||||||
|
self._output_file = open(self.path, 'wb')
|
||||||
|
atexit.register(self._output_file.close)
|
||||||
|
return self._output_file
|
||||||
|
|
||||||
|
def read_event(self):
|
||||||
|
data = self.input_file.read(struct.calcsize(event_bin_format))
|
||||||
|
seconds, microseconds, type, code, value = struct.unpack(event_bin_format, data)
|
||||||
|
return seconds + microseconds / 1e6, type, code, value, self.path
|
||||||
|
|
||||||
|
def write_event(self, type, code, value):
|
||||||
|
integer, fraction = divmod(now(), 1)
|
||||||
|
seconds = int(integer)
|
||||||
|
microseconds = int(fraction * 1e6)
|
||||||
|
data_event = struct.pack(event_bin_format, seconds, microseconds, type, code, value)
|
||||||
|
|
||||||
|
# Send a sync event to ensure other programs update.
|
||||||
|
sync_event = struct.pack(event_bin_format, seconds, microseconds, EV_SYN, 0, 0)
|
||||||
|
|
||||||
|
self.output_file.write(data_event + sync_event)
|
||||||
|
self.output_file.flush()
|
||||||
|
|
||||||
|
class AggregatedEventDevice(object):
|
||||||
|
def __init__(self, devices, output=None):
|
||||||
|
self.event_queue = Queue()
|
||||||
|
self.devices = devices
|
||||||
|
self.output = output or self.devices[0]
|
||||||
|
def start_reading(device):
|
||||||
|
while True:
|
||||||
|
self.event_queue.put(device.read_event())
|
||||||
|
for device in self.devices:
|
||||||
|
thread = Thread(target=start_reading, args=[device])
|
||||||
|
thread.setDaemon(True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def read_event(self):
|
||||||
|
return self.event_queue.get(block=True)
|
||||||
|
|
||||||
|
def write_event(self, type, code, value):
|
||||||
|
self.output.write_event(type, code, value)
|
||||||
|
|
||||||
|
import re
|
||||||
|
from collections import namedtuple
|
||||||
|
DeviceDescription = namedtuple('DeviceDescription', 'event_file is_mouse is_keyboard')
|
||||||
|
device_pattern = r"""N: Name="([^"]+?)".+?H: Handlers=([^\n]+)"""
|
||||||
|
def list_devices_from_proc(type_name):
|
||||||
|
try:
|
||||||
|
with open('/proc/bus/input/devices') as f:
|
||||||
|
description = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return
|
||||||
|
|
||||||
|
devices = {}
|
||||||
|
for name, handlers in re.findall(device_pattern, description, re.DOTALL):
|
||||||
|
path = '/dev/input/event' + re.search(r'event(\d+)', handlers).group(1)
|
||||||
|
if type_name in handlers:
|
||||||
|
yield EventDevice(path)
|
||||||
|
|
||||||
|
def list_devices_from_by_id(type_name):
|
||||||
|
for path in glob('/dev/input/by-id/*-event-' + type_name):
|
||||||
|
yield EventDevice(path)
|
||||||
|
|
||||||
|
def aggregate_devices(type_name):
|
||||||
|
# Some systems have multiple keyboards with different range of allowed keys
|
||||||
|
# on each one, like a notebook with a "keyboard" device exclusive for the
|
||||||
|
# power button. Instead of figuring out which keyboard allows which key to
|
||||||
|
# send events, we create a fake device and send all events through there.
|
||||||
|
uinput = make_uinput()
|
||||||
|
fake_device = EventDevice('uinput Fake Device')
|
||||||
|
fake_device._input_file = uinput
|
||||||
|
fake_device._output_file = uinput
|
||||||
|
|
||||||
|
# We don't aggregate devices from different sources to avoid
|
||||||
|
# duplicates.
|
||||||
|
|
||||||
|
devices_from_proc = list(list_devices_from_proc(type_name))
|
||||||
|
if devices_from_proc:
|
||||||
|
return AggregatedEventDevice(devices_from_proc, output=fake_device)
|
||||||
|
|
||||||
|
# breaks on mouse for virtualbox
|
||||||
|
# was getting /dev/input/by-id/usb-VirtualBox_USB_Tablet-event-mouse
|
||||||
|
devices_from_by_id = list(list_devices_from_by_id(type_name))
|
||||||
|
if devices_from_by_id:
|
||||||
|
return AggregatedEventDevice(devices_from_by_id, output=fake_device)
|
||||||
|
|
||||||
|
# If no keyboards were found we can only use the fake device to send keys.
|
||||||
|
return fake_device
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_root():
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
raise ImportError('You must be root to use this library on linux.')
|
@ -0,0 +1,131 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import struct
|
||||||
|
from subprocess import check_output
|
||||||
|
import re
|
||||||
|
from ._nixcommon import EV_KEY, EV_REL, EV_MSC, EV_SYN, EV_ABS, aggregate_devices, ensure_root
|
||||||
|
from ._mouse_event import ButtonEvent, WheelEvent, MoveEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
from ctypes import c_uint32, c_uint, c_int, c_void_p, byref
|
||||||
|
|
||||||
|
display = None
|
||||||
|
window = None
|
||||||
|
x11 = None
|
||||||
|
def build_display():
|
||||||
|
global display, window, x11
|
||||||
|
if display and window and x11: return
|
||||||
|
x11 = ctypes.cdll.LoadLibrary(ctypes.util.find_library('X11'))
|
||||||
|
# Required because we will have multiple threads calling x11,
|
||||||
|
# such as the listener thread and then main using "move_to".
|
||||||
|
x11.XInitThreads()
|
||||||
|
# Explicitly set XOpenDisplay.restype to avoid segfault on 64 bit OS.
|
||||||
|
# http://stackoverflow.com/questions/35137007/get-mouse-position-on-linux-pure-python
|
||||||
|
x11.XOpenDisplay.restype = c_void_p
|
||||||
|
display = c_void_p(x11.XOpenDisplay(0))
|
||||||
|
window = x11.XDefaultRootWindow(display)
|
||||||
|
|
||||||
|
def get_position():
|
||||||
|
build_display()
|
||||||
|
root_id, child_id = c_void_p(), c_void_p()
|
||||||
|
root_x, root_y, win_x, win_y = c_int(), c_int(), c_int(), c_int()
|
||||||
|
mask = c_uint()
|
||||||
|
ret = x11.XQueryPointer(display, c_uint32(window), byref(root_id), byref(child_id),
|
||||||
|
byref(root_x), byref(root_y),
|
||||||
|
byref(win_x), byref(win_y), byref(mask))
|
||||||
|
return root_x.value, root_y.value
|
||||||
|
|
||||||
|
def move_to(x, y):
|
||||||
|
build_display()
|
||||||
|
x11.XWarpPointer(display, None, window, 0, 0, 0, 0, x, y)
|
||||||
|
x11.XFlush(display)
|
||||||
|
|
||||||
|
REL_X = 0x00
|
||||||
|
REL_Y = 0x01
|
||||||
|
REL_Z = 0x02
|
||||||
|
REL_HWHEEL = 0x06
|
||||||
|
REL_WHEEL = 0x08
|
||||||
|
|
||||||
|
ABS_X = 0x00
|
||||||
|
ABS_Y = 0x01
|
||||||
|
|
||||||
|
BTN_MOUSE = 0x110
|
||||||
|
BTN_LEFT = 0x110
|
||||||
|
BTN_RIGHT = 0x111
|
||||||
|
BTN_MIDDLE = 0x112
|
||||||
|
BTN_SIDE = 0x113
|
||||||
|
BTN_EXTRA = 0x114
|
||||||
|
|
||||||
|
button_by_code = {
|
||||||
|
BTN_LEFT: LEFT,
|
||||||
|
BTN_RIGHT: RIGHT,
|
||||||
|
BTN_MIDDLE: MIDDLE,
|
||||||
|
BTN_SIDE: X,
|
||||||
|
BTN_EXTRA: X2,
|
||||||
|
}
|
||||||
|
code_by_button = {button: code for code, button in button_by_code.items()}
|
||||||
|
|
||||||
|
device = None
|
||||||
|
def build_device():
|
||||||
|
global device
|
||||||
|
if device: return
|
||||||
|
ensure_root()
|
||||||
|
device = aggregate_devices('mouse')
|
||||||
|
init = build_device
|
||||||
|
|
||||||
|
def listen(queue):
|
||||||
|
build_device()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time, type, code, value, device_id = device.read_event()
|
||||||
|
if type == EV_SYN or type == EV_MSC:
|
||||||
|
continue
|
||||||
|
|
||||||
|
event = None
|
||||||
|
arg = None
|
||||||
|
|
||||||
|
if type == EV_KEY:
|
||||||
|
event = ButtonEvent(DOWN if value else UP, button_by_code.get(code, '?'), time)
|
||||||
|
elif type == EV_REL:
|
||||||
|
value, = struct.unpack('i', struct.pack('I', value))
|
||||||
|
|
||||||
|
if code == REL_WHEEL:
|
||||||
|
event = WheelEvent(value, time)
|
||||||
|
elif code in (REL_X, REL_Y):
|
||||||
|
x, y = get_position()
|
||||||
|
event = MoveEvent(x, y, time)
|
||||||
|
|
||||||
|
if event is None:
|
||||||
|
# Unknown event type.
|
||||||
|
continue
|
||||||
|
|
||||||
|
queue.put(event)
|
||||||
|
|
||||||
|
def press(button=LEFT):
|
||||||
|
build_device()
|
||||||
|
device.write_event(EV_KEY, code_by_button[button], 0x01)
|
||||||
|
|
||||||
|
def release(button=LEFT):
|
||||||
|
build_device()
|
||||||
|
device.write_event(EV_KEY, code_by_button[button], 0x00)
|
||||||
|
|
||||||
|
def move_relative(x, y):
|
||||||
|
build_device()
|
||||||
|
# Note relative events are not in terms of pixels, but millimeters.
|
||||||
|
if x < 0:
|
||||||
|
x += 2**32
|
||||||
|
if y < 0:
|
||||||
|
y += 2**32
|
||||||
|
device.write_event(EV_REL, REL_X, x)
|
||||||
|
device.write_event(EV_REL, REL_Y, y)
|
||||||
|
|
||||||
|
def wheel(delta=1):
|
||||||
|
build_device()
|
||||||
|
if delta < 0:
|
||||||
|
delta += 2**32
|
||||||
|
device.write_event(EV_REL, REL_WHEEL, delta)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
#listen(print)
|
||||||
|
move_to(100, 200)
|
@ -0,0 +1,216 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import ctypes
|
||||||
|
import time
|
||||||
|
from ctypes import c_short, c_char, c_uint8, c_int32, c_int, c_uint, c_uint32, c_long, byref, Structure, CFUNCTYPE, POINTER
|
||||||
|
from ctypes.wintypes import DWORD, BOOL, HHOOK, MSG, LPWSTR, WCHAR, WPARAM, LPARAM
|
||||||
|
LPMSG = POINTER(MSG)
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
|
||||||
|
from ._mouse_event import ButtonEvent, WheelEvent, MoveEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE, WHEEL, HORIZONTAL, VERTICAL
|
||||||
|
|
||||||
|
#user32 = ctypes.windll.user32
|
||||||
|
user32 = ctypes.WinDLL('user32', use_last_error = True)
|
||||||
|
|
||||||
|
class MSLLHOOKSTRUCT(Structure):
|
||||||
|
_fields_ = [("x", c_long),
|
||||||
|
("y", c_long),
|
||||||
|
('data', c_int32),
|
||||||
|
('reserved', c_int32),
|
||||||
|
("flags", DWORD),
|
||||||
|
("time", c_int),
|
||||||
|
]
|
||||||
|
|
||||||
|
LowLevelMouseProc = CFUNCTYPE(c_int, WPARAM, LPARAM, POINTER(MSLLHOOKSTRUCT))
|
||||||
|
|
||||||
|
SetWindowsHookEx = user32.SetWindowsHookExA
|
||||||
|
#SetWindowsHookEx.argtypes = [c_int, LowLevelMouseProc, c_int, c_int]
|
||||||
|
SetWindowsHookEx.restype = HHOOK
|
||||||
|
|
||||||
|
CallNextHookEx = user32.CallNextHookEx
|
||||||
|
#CallNextHookEx.argtypes = [c_int , c_int, c_int, POINTER(MSLLHOOKSTRUCT)]
|
||||||
|
CallNextHookEx.restype = c_int
|
||||||
|
|
||||||
|
UnhookWindowsHookEx = user32.UnhookWindowsHookEx
|
||||||
|
UnhookWindowsHookEx.argtypes = [HHOOK]
|
||||||
|
UnhookWindowsHookEx.restype = BOOL
|
||||||
|
|
||||||
|
GetMessage = user32.GetMessageW
|
||||||
|
GetMessage.argtypes = [LPMSG, c_int, c_int, c_int]
|
||||||
|
GetMessage.restype = BOOL
|
||||||
|
|
||||||
|
TranslateMessage = user32.TranslateMessage
|
||||||
|
TranslateMessage.argtypes = [LPMSG]
|
||||||
|
TranslateMessage.restype = BOOL
|
||||||
|
|
||||||
|
DispatchMessage = user32.DispatchMessageA
|
||||||
|
DispatchMessage.argtypes = [LPMSG]
|
||||||
|
|
||||||
|
GetDoubleClickTime = user32.GetDoubleClickTime
|
||||||
|
|
||||||
|
# Beware, as of 2016-01-30 the official docs have a very incomplete list.
|
||||||
|
# This one was compiled from experience and may be incomplete.
|
||||||
|
WM_MOUSEMOVE = 0x200
|
||||||
|
WM_LBUTTONDOWN = 0x201
|
||||||
|
WM_LBUTTONUP = 0x202
|
||||||
|
WM_LBUTTONDBLCLK = 0x203
|
||||||
|
WM_RBUTTONDOWN = 0x204
|
||||||
|
WM_RBUTTONUP = 0x205
|
||||||
|
WM_RBUTTONDBLCLK = 0x206
|
||||||
|
WM_MBUTTONDOWN = 0x207
|
||||||
|
WM_MBUTTONUP = 0x208
|
||||||
|
WM_MBUTTONDBLCLK = 0x209
|
||||||
|
WM_MOUSEWHEEL = 0x20A
|
||||||
|
WM_XBUTTONDOWN = 0x20B
|
||||||
|
WM_XBUTTONUP = 0x20C
|
||||||
|
WM_XBUTTONDBLCLK = 0x20D
|
||||||
|
WM_NCXBUTTONDOWN = 0x00AB
|
||||||
|
WM_NCXBUTTONUP = 0x00AC
|
||||||
|
WM_NCXBUTTONDBLCLK = 0x00AD
|
||||||
|
WM_MOUSEHWHEEL = 0x20E
|
||||||
|
WM_LBUTTONDOWN = 0x0201
|
||||||
|
WM_LBUTTONUP = 0x0202
|
||||||
|
WM_MOUSEMOVE = 0x0200
|
||||||
|
WM_MOUSEWHEEL = 0x020A
|
||||||
|
WM_MOUSEHWHEEL = 0x020E
|
||||||
|
WM_RBUTTONDOWN = 0x0204
|
||||||
|
WM_RBUTTONUP = 0x0205
|
||||||
|
|
||||||
|
buttons_by_wm_code = {
|
||||||
|
WM_LBUTTONDOWN: (DOWN, LEFT),
|
||||||
|
WM_LBUTTONUP: (UP, LEFT),
|
||||||
|
WM_LBUTTONDBLCLK: (DOUBLE, LEFT),
|
||||||
|
|
||||||
|
WM_RBUTTONDOWN: (DOWN, RIGHT),
|
||||||
|
WM_RBUTTONUP: (UP, RIGHT),
|
||||||
|
WM_RBUTTONDBLCLK: (DOUBLE, RIGHT),
|
||||||
|
|
||||||
|
WM_MBUTTONDOWN: (DOWN, MIDDLE),
|
||||||
|
WM_MBUTTONUP: (UP, MIDDLE),
|
||||||
|
WM_MBUTTONDBLCLK: (DOUBLE, MIDDLE),
|
||||||
|
|
||||||
|
WM_XBUTTONDOWN: (DOWN, X),
|
||||||
|
WM_XBUTTONUP: (UP, X),
|
||||||
|
WM_XBUTTONDBLCLK: (DOUBLE, X),
|
||||||
|
}
|
||||||
|
|
||||||
|
MOUSEEVENTF_ABSOLUTE = 0x8000
|
||||||
|
MOUSEEVENTF_MOVE = 0x1
|
||||||
|
MOUSEEVENTF_WHEEL = 0x800
|
||||||
|
MOUSEEVENTF_HWHEEL = 0x1000
|
||||||
|
MOUSEEVENTF_LEFTDOWN = 0x2
|
||||||
|
MOUSEEVENTF_LEFTUP = 0x4
|
||||||
|
MOUSEEVENTF_RIGHTDOWN = 0x8
|
||||||
|
MOUSEEVENTF_RIGHTUP = 0x10
|
||||||
|
MOUSEEVENTF_MIDDLEDOWN = 0x20
|
||||||
|
MOUSEEVENTF_MIDDLEUP = 0x40
|
||||||
|
MOUSEEVENTF_XDOWN = 0x0080
|
||||||
|
MOUSEEVENTF_XUP = 0x0100
|
||||||
|
|
||||||
|
simulated_mouse_codes = {
|
||||||
|
(WHEEL, HORIZONTAL): MOUSEEVENTF_HWHEEL,
|
||||||
|
(WHEEL, VERTICAL): MOUSEEVENTF_WHEEL,
|
||||||
|
|
||||||
|
(DOWN, LEFT): MOUSEEVENTF_LEFTDOWN,
|
||||||
|
(UP, LEFT): MOUSEEVENTF_LEFTUP,
|
||||||
|
|
||||||
|
(DOWN, RIGHT): MOUSEEVENTF_RIGHTDOWN,
|
||||||
|
(UP, RIGHT): MOUSEEVENTF_RIGHTUP,
|
||||||
|
|
||||||
|
(DOWN, MIDDLE): MOUSEEVENTF_MIDDLEDOWN,
|
||||||
|
(UP, MIDDLE): MOUSEEVENTF_MIDDLEUP,
|
||||||
|
|
||||||
|
(DOWN, X): MOUSEEVENTF_XDOWN,
|
||||||
|
(UP, X): MOUSEEVENTF_XUP,
|
||||||
|
}
|
||||||
|
|
||||||
|
NULL = c_int(0)
|
||||||
|
|
||||||
|
WHEEL_DELTA = 120
|
||||||
|
|
||||||
|
init = lambda: None
|
||||||
|
|
||||||
|
previous_button_event = None # defined in global scope
|
||||||
|
def listen(queue):
|
||||||
|
|
||||||
|
def low_level_mouse_handler(nCode, wParam, lParam):
|
||||||
|
global previous_button_event
|
||||||
|
|
||||||
|
struct = lParam.contents
|
||||||
|
# Can't use struct.time because it's usually zero.
|
||||||
|
t = time.time()
|
||||||
|
|
||||||
|
if wParam == WM_MOUSEMOVE:
|
||||||
|
event = MoveEvent(struct.x, struct.y, t)
|
||||||
|
elif wParam == WM_MOUSEWHEEL:
|
||||||
|
event = WheelEvent(struct.data / (WHEEL_DELTA * (2<<15)), t)
|
||||||
|
elif wParam in buttons_by_wm_code:
|
||||||
|
type, button = buttons_by_wm_code.get(wParam, ('?', '?'))
|
||||||
|
if wParam >= WM_XBUTTONDOWN:
|
||||||
|
button = {0x10000: X, 0x20000: X2}[struct.data]
|
||||||
|
event = ButtonEvent(type, button, t)
|
||||||
|
|
||||||
|
if (event.event_type == DOWN) and previous_button_event is not None:
|
||||||
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/gg153548%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
|
||||||
|
if event.time - previous_button_event.time <= GetDoubleClickTime() / 1000.0:
|
||||||
|
event = ButtonEvent(DOUBLE, event.button, event.time)
|
||||||
|
|
||||||
|
previous_button_event = event
|
||||||
|
else:
|
||||||
|
# Unknown event type.
|
||||||
|
return CallNextHookEx(NULL, nCode, wParam, lParam)
|
||||||
|
|
||||||
|
queue.put(event)
|
||||||
|
return CallNextHookEx(NULL, nCode, wParam, lParam)
|
||||||
|
|
||||||
|
WH_MOUSE_LL = c_int(14)
|
||||||
|
mouse_callback = LowLevelMouseProc(low_level_mouse_handler)
|
||||||
|
mouse_hook = SetWindowsHookEx(WH_MOUSE_LL, mouse_callback, NULL, NULL)
|
||||||
|
|
||||||
|
# Register to remove the hook when the interpreter exits. Unfortunately a
|
||||||
|
# try/finally block doesn't seem to work here.
|
||||||
|
atexit.register(UnhookWindowsHookEx, mouse_hook)
|
||||||
|
|
||||||
|
msg = LPMSG()
|
||||||
|
while not GetMessage(msg, NULL, NULL, NULL):
|
||||||
|
TranslateMessage(msg)
|
||||||
|
DispatchMessage(msg)
|
||||||
|
|
||||||
|
def _translate_button(button):
|
||||||
|
if button == X or button == X2:
|
||||||
|
return X, {X: 0x10000, X2: 0x20000}[button]
|
||||||
|
else:
|
||||||
|
return button, 0
|
||||||
|
|
||||||
|
def press(button=LEFT):
|
||||||
|
button, data = _translate_button(button)
|
||||||
|
code = simulated_mouse_codes[(DOWN, button)]
|
||||||
|
user32.mouse_event(code, 0, 0, data, 0)
|
||||||
|
|
||||||
|
def release(button=LEFT):
|
||||||
|
button, data = _translate_button(button)
|
||||||
|
code = simulated_mouse_codes[(UP, button)]
|
||||||
|
user32.mouse_event(code, 0, 0, data, 0)
|
||||||
|
|
||||||
|
def wheel(delta=1):
|
||||||
|
code = simulated_mouse_codes[(WHEEL, VERTICAL)]
|
||||||
|
user32.mouse_event(code, 0, 0, int(delta * WHEEL_DELTA), 0)
|
||||||
|
|
||||||
|
def move_to(x, y):
|
||||||
|
user32.SetCursorPos(int(x), int(y))
|
||||||
|
|
||||||
|
def move_relative(x, y):
|
||||||
|
user32.mouse_event(MOUSEEVENTF_MOVE, int(x), int(y), 0, 0)
|
||||||
|
|
||||||
|
class POINT(Structure):
|
||||||
|
_fields_ = [("x", c_long), ("y", c_long)]
|
||||||
|
|
||||||
|
def get_position():
|
||||||
|
point = POINT()
|
||||||
|
user32.GetCursorPos(byref(point))
|
||||||
|
return (point.x, point.y)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
def p(e):
|
||||||
|
print(e)
|
||||||
|
listen(p)
|
@ -0,0 +1,125 @@
|
|||||||
|
import datetime # datetime functions
|
||||||
|
import time # time functions
|
||||||
|
# Init the keyboard listener
|
||||||
|
def Init(inGSettings):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# Technical defs
|
||||||
|
import pdb
|
||||||
|
SpecialKeyList = []
|
||||||
|
inList = [
|
||||||
|
# {
|
||||||
|
# "EventStartDatetime": datetime.datetime.now(),
|
||||||
|
# "EventEndDatetime": datetime.datetime.now(),
|
||||||
|
# "EventTypeStr": "MOVE", # MOVE | CLICK | SCROLL | SELECT
|
||||||
|
# "ValueInt": "" # MOVE: point count
|
||||||
|
# }
|
||||||
|
]
|
||||||
|
gMoveWaitNextPointPercentFloat = 4
|
||||||
|
gMoveWaitNextPointSecondsFloat = None
|
||||||
|
gMovePrevious = None # {"EventDatetime": datetime.datetime.now(), "XInt": None, "YInt": None}
|
||||||
|
gMovePointCountInt = None
|
||||||
|
gMoveFirstDatetime = None
|
||||||
|
gMoveCloseCheckIntervalSecondsFloat = 3
|
||||||
|
|
||||||
|
from pynput import mouse
|
||||||
|
import mouse as mouse_new # https://github.com/boppreh/mouse#mouse.hook
|
||||||
|
def on_move(x, y):
|
||||||
|
global gMovePrevious
|
||||||
|
global gMoveWaitNextPointSecondsFloat
|
||||||
|
global gMoveWaitNextPointPercentFloat
|
||||||
|
global gMovePointCountInt
|
||||||
|
global gMoveFirstDatetime
|
||||||
|
lNowDatetime = datetime.datetime.now()
|
||||||
|
if gMovePrevious is None:
|
||||||
|
gMoveFirstDatetime = lNowDatetime
|
||||||
|
gMovePrevious = {"EventDatetime": gMoveFirstDatetime, "XInt": x, "YInt": y}
|
||||||
|
gMovePointCountInt = 1
|
||||||
|
else:
|
||||||
|
lMovePreviousNew = {"EventDatetime": lNowDatetime, "XInt": x, "YInt": y}
|
||||||
|
lNewOldDeltaSecondsFloat = (lMovePreviousNew["EventDatetime"]-gMovePrevious["EventDatetime"]).total_seconds()
|
||||||
|
if gMoveWaitNextPointSecondsFloat is None: # We have no wait time - just calculate it
|
||||||
|
gMoveWaitNextPointSecondsFloat = lNewOldDeltaSecondsFloat * gMoveWaitNextPointPercentFloat # in the end for the next point
|
||||||
|
gMovePrevious = {"EventDatetime": lNowDatetime, "XInt": x, "YInt": y}
|
||||||
|
gMovePointCountInt = gMovePointCountInt + 1
|
||||||
|
else: # check if we apply in time - else create new move record
|
||||||
|
if gMoveWaitNextPointSecondsFloat >= lNewOldDeltaSecondsFloat: # In applied second wait range
|
||||||
|
# Rewrite the globals
|
||||||
|
gMoveWaitNextPointSecondsFloat = lNewOldDeltaSecondsFloat * gMoveWaitNextPointPercentFloat # in the end for the next point
|
||||||
|
gMovePrevious = {"EventDatetime": lNowDatetime, "XInt": x, "YInt": y}
|
||||||
|
gMovePointCountInt = gMovePointCountInt + 1
|
||||||
|
else:
|
||||||
|
# Add in result list
|
||||||
|
lResult = {
|
||||||
|
"EventStartDatetime": gMoveFirstDatetime,
|
||||||
|
"EventEndDatetime": lNowDatetime,
|
||||||
|
"EventTypeStr": "MOVE", # MOVE | CLICK | SCROLL | SELECT
|
||||||
|
"ValueInt": gMovePointCountInt # MOVE: point count
|
||||||
|
}
|
||||||
|
inList.append(lResult)
|
||||||
|
print(lResult)
|
||||||
|
# Close the move
|
||||||
|
gMoveWaitNextPointSecondsFloat = None
|
||||||
|
gMovePrevious = None
|
||||||
|
gMovePointCountInt = None
|
||||||
|
gMoveFirstDatetime= None
|
||||||
|
#print('Pointer moved to {0}'.format(
|
||||||
|
# (x, y)))
|
||||||
|
|
||||||
|
def on_click(x, y, button, pressed):
|
||||||
|
print('{0} at {1}'.format(
|
||||||
|
'Pressed' if pressed else 'Released',
|
||||||
|
(x, y)))
|
||||||
|
print(button)
|
||||||
|
if not pressed:
|
||||||
|
# Stop listener
|
||||||
|
return True
|
||||||
|
|
||||||
|
def on_scroll(x, y, dx, dy):
|
||||||
|
print('Scrolled {0} at {1}'.format(
|
||||||
|
'down' if dy < 0 else 'up',
|
||||||
|
(x, y)))
|
||||||
|
|
||||||
|
# Collect events until released
|
||||||
|
#with mouse.Listener(
|
||||||
|
# on_move=on_move,
|
||||||
|
# on_click=on_click,
|
||||||
|
# on_scroll=on_scroll) as listener:
|
||||||
|
# listener.join()
|
||||||
|
|
||||||
|
# ...or, in a non-blocking fashion:
|
||||||
|
#listener = mouse.Listener(
|
||||||
|
# on_move=on_move,
|
||||||
|
# on_click=on_click,
|
||||||
|
# on_scroll=on_scroll)
|
||||||
|
#listener.start()
|
||||||
|
def Test(inArg):
|
||||||
|
if type(inArg) is mouse_new._mouse_event.MoveEvent:
|
||||||
|
on_move(x = inArg.x, y = inArg.y)
|
||||||
|
mouse_new.hook(Test)
|
||||||
|
|
||||||
|
import pprint
|
||||||
|
while True:
|
||||||
|
# Check move interval
|
||||||
|
lNowDatetime = datetime.datetime.now()
|
||||||
|
if gMovePrevious is not None and gMoveWaitNextPointSecondsFloat is not None:
|
||||||
|
lNewOldDeltaSecondsFloat = (lNowDatetime - gMovePrevious["EventDatetime"]).total_seconds()
|
||||||
|
if lNewOldDeltaSecondsFloat > gMoveWaitNextPointSecondsFloat:
|
||||||
|
# Add in result list
|
||||||
|
lResult = {
|
||||||
|
"EventStartDatetime": gMoveFirstDatetime,
|
||||||
|
"EventEndDatetime": lNowDatetime,
|
||||||
|
"EventTypeStr": "MOVE", # MOVE | CLICK | SCROLL | SELECT
|
||||||
|
"ValueInt": gMovePointCountInt # MOVE: point count
|
||||||
|
}
|
||||||
|
inList.append(lResult)
|
||||||
|
print(lResult)
|
||||||
|
# Close the move
|
||||||
|
gMoveWaitNextPointSecondsFloat = None
|
||||||
|
gMovePrevious = None
|
||||||
|
gMovePointCountInt = None
|
||||||
|
gMoveFirstDatetime = None
|
||||||
|
time.sleep(gMoveCloseCheckIntervalSecondsFloat)
|
||||||
|
#print(inList)
|
||||||
|
#pprint.pprint(inList)
|
Loading…
Reference in new issue