diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..8e6f19ca
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,62 @@
+name: CI
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-python@v6
+ with:
+ python-version: '3.13'
+ - run: pip install ruff
+ - name: ruff format --check
+ run: |
+ ruff format --check \
+ --config 'format.quote-style="preserve"' \
+ --config 'line-length=80' \
+ lib/pynput tests
+
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ python: ['3.10', '3.11', '3.12', '3.13']
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-python@v6
+ with:
+ python-version: ${{ matrix.python }}
+ - name: Install pynput
+ run: pip install -e .
+ - name: Install Linux backend deps
+ if: runner.os == 'Linux'
+ run: pip install evdev python-xlib
+ - name: Run headless test subset
+ env:
+ # Tests in this subset don't synthesize OS input and don't wait
+ # for human input via self.confirm() — safe to run on a CI runner.
+ # The rest of the suite needs a real interactive session.
+ PYNPUT_TESTS: >-
+ tests.keyboard_hotkey_tests.KeyboardHotKeyTest.test_parse_valid
+ tests.keyboard_hotkey_tests.KeyboardHotKeyTest.test_parse_invalid
+ tests.keyboard_hotkey_tests.KeyboardHotKeyTest.test_activate_single
+ tests.keyboard_hotkey_tests.KeyboardHotKeyTest.test_activate_combo
+ tests.mouse_controller_tests.MouseControllerTest.test_buttons
+ tests.keyboard_controller_tests.KeyboardControllerTest.test_keys
+ tests.keyboard_controller_tests.KeyboardControllerTest.test_press_invalid
+ tests.keyboard_controller_tests.KeyboardControllerTest.test_release_invalid
+ shell: bash
+ run: |
+ if [ "$RUNNER_OS" = "Linux" ]; then
+ xvfb-run -a python -m unittest $PYNPUT_TESTS
+ else
+ python -m unittest $PYNPUT_TESTS
+ fi
diff --git a/lib/pynput/_info.py b/lib/pynput/_info.py
index 47d1e5b6..a7591ae6 100644
--- a/lib/pynput/_info.py
+++ b/lib/pynput/_info.py
@@ -15,5 +15,5 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
-__author__ = u'Moses Palmér'
+__author__ = 'Moses Palmér'
__version__ = (1, 8, 2)
diff --git a/tests/__init__.py b/tests/__init__.py
index b3662446..6a7727b3 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -62,8 +62,11 @@ def notify(message, delay=None, columns=50):
if lines:
lines.append('')
for word in line.split():
- if not lines or not lines[-1] \
- or len(lines[-1]) + 1 + len(word) > max_length:
+ if (
+ not lines
+ or not lines[-1]
+ or len(lines[-1]) + 1 + len(word) > max_length
+ ):
lines.append(word)
else:
lines[-1] += ' ' + word
@@ -79,7 +82,6 @@ def notify(message, delay=None, columns=50):
time.sleep(delay)
-
#: A decorator to make a test run only on macOS
darwin = functools.partial(_backend, 'darwin')
@@ -119,7 +121,8 @@ def tearDownClass(self):
remaining = [
listener
for listener in self.listeners
- if not (listener.join(0.5) or listener.is_alive)]
+ if not (listener.join(0.5) or listener.is_alive)
+ ]
for listener in remaining:
listener.join()
@@ -152,6 +155,7 @@ def assert_event(self, failure_message, **kwargs):
:param kwargs: Arguments to pass to the listener constructor.
"""
+
def wrapper(name, callback):
def inner(*a):
if callback(*a):
@@ -160,9 +164,12 @@ def inner(*a):
return inner if callback else None
- with self.listener(**{
+ with self.listener(
+ **{
name: wrapper(name, callback)
- for name, callback in kwargs.items()}) as listener:
+ for name, callback in kwargs.items()
+ }
+ ) as listener:
time.sleep(0.1)
listener.success = False
yield
@@ -172,9 +179,7 @@ def inner(*a):
if listener.success:
break
- self.assertTrue(
- listener.success,
- failure_message)
+ self.assertTrue(listener.success, failure_message)
def assert_stop(self, failure_message, **callbacks):
"""Asserts that a listener stop within :attr:`STOP_MAX_WAIT` seconds.
@@ -195,9 +200,7 @@ def assert_stop(self, failure_message, **callbacks):
success = True
break
- self.assertTrue(
- success,
- failure_message)
+ self.assertTrue(success, failure_message)
def assert_cumulative(self, failure_message, **callbacks):
"""Asserts that the callback returns true for at least two thirds of
@@ -211,9 +214,7 @@ def assert_cumulative(self, failure_message, **callbacks):
occurred.
"""
# The lists of accumulated events
- events = {
- name: []
- for name in callbacks}
+ events = {name: [] for name in callbacks}
def wrapper(name, callback):
def inner(*a):
@@ -222,19 +223,26 @@ def inner(*a):
total_length = len(cache)
if total_length > self.CHANGE_MIN_EVENTS:
- change_length = len([
- None
- for i, b in enumerate(cache[1:])
- if callback(cache[i], b)])
+ change_length = len(
+ [
+ None
+ for i, b in enumerate(cache[1:])
+ if callback(cache[i], b)
+ ]
+ )
if change_length > (2 * total_length) / 3:
return False
return inner if callback else None
- self.assert_stop(failure_message, **{
- name: wrapper(name, callback)
- for name, callback in callbacks.items()})
+ self.assert_stop(
+ failure_message,
+ **{
+ name: wrapper(name, callback)
+ for name, callback in callbacks.items()
+ },
+ )
def confirm(self, statement, *fmt):
"""Asks the user to confirm a statement.
@@ -251,10 +259,13 @@ def confirm(self, statement, *fmt):
response = input(message)
if response.lower() in valid_responses:
self.assertIn(
- response.lower(), accept_responses,
- 'User declined statement "%s"' % message)
+ response.lower(),
+ accept_responses,
+ 'User declined statement "%s"' % message,
+ )
return
else:
print(
- 'Please respond %s' % ', '.join(
- '"%s"' % r for r in valid_responses))
+ 'Please respond %s'
+ % ', '.join('"%s"' % r for r in valid_responses)
+ )
diff --git a/tests/keyboard_controller_tests.py b/tests/keyboard_controller_tests.py
index e1e0ad63..c59eba0c 100644
--- a/tests/keyboard_controller_tests.py
+++ b/tests/keyboard_controller_tests.py
@@ -31,7 +31,8 @@ class KeyboardControllerTest(EventTest):
NOTIFICATION = (
'This test case is non-interactive, so you must not use the '
'keyboard.\n'
- 'You must, however, keep this window focused.')
+ 'You must, however, keep this window focused.'
+ )
CONTROLLER_CLASS = pynput.keyboard.Controller
LISTENER_CLASS = pynput.keyboard.Listener
@@ -44,9 +45,10 @@ def decode(self, string):
yield string
else:
for encoding in (
- 'utf-8',
- locale.getpreferredencoding(),
- sys.stdin.encoding):
+ 'utf-8',
+ locale.getpreferredencoding(),
+ sys.stdin.encoding,
+ ):
if encoding:
try:
yield string.decode(encoding)
@@ -65,6 +67,7 @@ def capture(self):
def reader():
while reader.running:
data.append(sys.stdin.readline()[:-1])
+
reader.running = True
# Start the thread
@@ -97,10 +100,12 @@ def test_keys(self):
"""Asserts that all keys defined for the base keyboard interface are
defined for the current platform"""
from pynput.keyboard._base import Key
+
for key in Key:
self.assertTrue(
hasattr(pynput.keyboard.Key, key.name),
- '%s is not defined for the current platform' % key.name)
+ '%s is not defined for the current platform' % key.name,
+ )
def test_press_invalid(self):
with self.assertRaises(self.controller.InvalidKeyException):
@@ -116,10 +121,7 @@ def test_press_release(self):
with self.capture() as collect:
self.controller.tap(pynput.keyboard.Key.space)
- self.assertIn(
- u' ',
- collect(),
- 'Failed to press and release space')
+ self.assertIn(' ', collect(), 'Failed to press and release space')
def test_touch(self):
"""Asserts that the touch shortcut behaves as expected"""
@@ -127,177 +129,158 @@ def test_touch(self):
self.controller.touch(pynput.keyboard.Key.space, True)
self.controller.touch(pynput.keyboard.Key.space, False)
- self.assertIn(
- u' ',
- collect(),
- 'Failed to press and release space')
+ self.assertIn(' ', collect(), 'Failed to press and release space')
def test_touch_dead(self):
"""Asserts that pressing dead keys generate combined characters"""
with self.capture() as collect:
- dead = pynput.keyboard.KeyCode.from_dead(u'~')
+ dead = pynput.keyboard.KeyCode.from_dead('~')
self.controller.tap(dead)
- self.controller.tap(u'a')
+ self.controller.tap('a')
- self.assertIn(
- u'ã',
- collect(),
- 'Failed to apply dead key')
+ self.assertIn('ã', collect(), 'Failed to apply dead key')
def test_touch_dead_space(self):
"""Asserts that pressing dead keys followed by space yields the
non-dead version"""
with self.capture() as collect:
- dead = pynput.keyboard.KeyCode.from_dead(u'~')
+ dead = pynput.keyboard.KeyCode.from_dead('~')
self.controller.tap(dead)
self.controller.tap(pynput.keyboard.Key.space)
- self.assertIn(
- u'~',
- collect(),
- 'Failed to apply dead key')
+ self.assertIn('~', collect(), 'Failed to apply dead key')
def test_touch_dead_twice(self):
"""Asserts that pressing dead keys twice yields the non-dead version"""
with self.capture() as collect:
- dead = pynput.keyboard.KeyCode.from_dead(u'~')
+ dead = pynput.keyboard.KeyCode.from_dead('~')
self.controller.tap(dead)
self.controller.tap(dead)
- self.assertIn(
- u'~',
- collect(),
- 'Failed to apply dead key')
+ self.assertIn('~', collect(), 'Failed to apply dead key')
def test_alt_pressed(self):
"""Asserts that alt_pressed works"""
# We do not test alt_r, since that does not necessarily exist on the
# keyboard
- for key in (
- pynput.keyboard.Key.alt,
- pynput.keyboard.Key.alt_l):
+ for key in (pynput.keyboard.Key.alt, pynput.keyboard.Key.alt_l):
self.controller.press(key)
self.assertTrue(
self.controller.alt_pressed,
- 'alt_pressed was not set with %s down' % key.name)
+ 'alt_pressed was not set with %s down' % key.name,
+ )
self.controller.release(key)
self.assertFalse(
- self.controller.alt_pressed,
- 'alt_pressed was incorrectly set')
+ self.controller.alt_pressed, 'alt_pressed was incorrectly set'
+ )
def test_ctrl_pressed(self):
"""Asserts that ctrl_pressed works"""
for key in (
- pynput.keyboard.Key.ctrl,
- pynput.keyboard.Key.ctrl_l,
- pynput.keyboard.Key.ctrl_r):
+ pynput.keyboard.Key.ctrl,
+ pynput.keyboard.Key.ctrl_l,
+ pynput.keyboard.Key.ctrl_r,
+ ):
self.controller.press(key)
self.assertTrue(
self.controller.ctrl_pressed,
- 'ctrl_pressed was not set with %s down' % key.name)
+ 'ctrl_pressed was not set with %s down' % key.name,
+ )
self.controller.release(key)
self.assertFalse(
- self.controller.ctrl_pressed,
- 'ctrl_pressed was incorrectly set')
+ self.controller.ctrl_pressed, 'ctrl_pressed was incorrectly set'
+ )
def test_shift_pressed(self):
"""Asserts that shift_pressed works with normal presses"""
for key in (
- pynput.keyboard.Key.shift,
- pynput.keyboard.Key.shift_l,
- pynput.keyboard.Key.shift_r):
+ pynput.keyboard.Key.shift,
+ pynput.keyboard.Key.shift_l,
+ pynput.keyboard.Key.shift_r,
+ ):
self.controller.press(key)
self.assertTrue(
self.controller.shift_pressed,
- 'shift_pressed was not set with %s down' % key.name)
+ 'shift_pressed was not set with %s down' % key.name,
+ )
self.controller.release(key)
self.assertFalse(
self.controller.shift_pressed,
- 'shift_pressed was incorrectly set')
+ 'shift_pressed was incorrectly set',
+ )
def test_shift_pressed_caps_lock(self):
"""Asserts that shift_pressed is True when caps lock is toggled"""
self.controller.tap(pynput.keyboard.Key.caps_lock)
self.assertTrue(
self.controller.shift_pressed,
- 'shift_pressed was not set with caps lock toggled')
+ 'shift_pressed was not set with caps lock toggled',
+ )
self.controller.tap(pynput.keyboard.Key.caps_lock)
self.assertFalse(
self.controller.shift_pressed,
- 'shift_pressed was not deactivated with caps lock toggled')
+ 'shift_pressed was not deactivated with caps lock toggled',
+ )
def test_pressed_shift(self):
"""Asserts that pressing and releasing a Latin character while pressing
shift causes it to shift to upper case"""
with self.capture() as collect:
with self.controller.pressed(pynput.keyboard.Key.shift):
- self.controller.tap(u'a')
+ self.controller.tap('a')
with self.controller.modifiers as modifiers:
- self.assertIn(
- pynput.keyboard.Key.shift,
- modifiers)
+ self.assertIn(pynput.keyboard.Key.shift, modifiers)
- self.assertIn(
- u'A',
- collect(),
- 'shift+a did not yield "A"')
+ self.assertIn('A', collect(), 'shift+a did not yield "A"')
def test_pressed_is_release(self):
"""Asserts that pressed actually releases the key"""
with self.capture() as collect:
with self.controller.pressed(pynput.keyboard.Key.shift):
- self.controller.tap(u'a')
+ self.controller.tap('a')
- self.controller.tap(u'a')
+ self.controller.tap('a')
with self.controller.pressed(pynput.keyboard.Key.shift):
- self.controller.tap(u'a')
+ self.controller.tap('a')
-
- self.assertIn(
- u'AaA',
- collect(),
- 'Keys were not properly released')
+ self.assertIn('AaA', collect(), 'Keys were not properly released')
def test_type_latin(self):
"""Asserts that type works for a Latin string"""
- self.assert_input(
- 'Failed to type latin string',
- u'Hello World')
+ self.assert_input('Failed to type latin string', 'Hello World')
def test_type_ascii(self):
"""Asserts that type works for an ascii string"""
- self.assert_input(
- 'Failed to type ascii string',
- u'abc123, "quoted!"')
+ self.assert_input('Failed to type ascii string', 'abc123, "quoted!"')
def test_type_nonascii(self):
"""Asserts that type works for a non-ascii strings"""
self.assert_input(
- 'Failed to type Spanish string',
- u'Teclado (informática)')
+ 'Failed to type Spanish string', 'Teclado (informática)'
+ )
self.assert_input(
- 'Failed to type Russian string',
- u'Компьютерная клавиатура')
+ 'Failed to type Russian string', 'Компьютерная клавиатура'
+ )
def test_type_control_codes(self):
"""Asserts that type works for a string containing control codes"""
- self.assert_input(
- 'Failed to type latin string',
- u'Hello\tworld')
+ self.assert_input('Failed to type latin string', 'Hello\tworld')
def test_controller_events(self):
"""Tests that events sent by a controller are received correctly"""
with self.assert_event(
- 'Failed to send press',
- on_press=lambda k: getattr(k, 'char', None) == u'a'):
- self.controller.press(u'a')
+ 'Failed to send press',
+ on_press=lambda k, injected: getattr(k, 'char', None) == 'a',
+ ):
+ self.controller.press('a')
with self.assert_event(
- 'Failed to send release',
- on_release=lambda k: getattr(k, 'char', None) == u'a'):
- self.controller.release(u'a')
+ 'Failed to send release',
+ on_release=lambda k, injected: getattr(k, 'char', None) == 'a',
+ ):
+ self.controller.release('a')
self.controller.tap(pynput.keyboard.Key.enter)
input()
diff --git a/tests/keyboard_hotkey_tests.py b/tests/keyboard_hotkey_tests.py
index f30b6328..f154ca78 100644
--- a/tests/keyboard_hotkey_tests.py
+++ b/tests/keyboard_hotkey_tests.py
@@ -52,33 +52,21 @@ def test_parse_invalid(self):
self.assertEqual(e.exception.args, ('+a+A',))
def test_parse_valid(self):
+ self.assertSequenceEqual(HotKey.parse('a'), [kc.from_char('a')])
+ self.assertSequenceEqual(HotKey.parse('A'), [kc.from_char('a')])
self.assertSequenceEqual(
- HotKey.parse('a'),
- [
- kc.from_char('a')])
+ HotKey.parse('+a'), [k.ctrl, kc.from_char('a')]
+ )
self.assertSequenceEqual(
- HotKey.parse('A'),
- [
- kc.from_char('a')])
+ HotKey.parse('++a'), [k.ctrl, k.alt, kc.from_char('a')]
+ )
self.assertSequenceEqual(
- HotKey.parse('+a'),
- [
- k.ctrl,
- kc.from_char('a')])
- self.assertSequenceEqual(
- HotKey.parse('++a'),
- [
- k.ctrl,
- k.alt,
- kc.from_char('a')])
- self.assertSequenceEqual(
- HotKey.parse('+<123456>'),
- [
- k.ctrl,
- kc.from_vk(123456)])
+ HotKey.parse('+<123456>'), [k.ctrl, kc.from_vk(123456)]
+ )
def test_activate_single(self):
activations = []
+
def on_activate():
activations.append(True)
@@ -103,6 +91,7 @@ def on_activate():
def test_activate_combo(self):
activations = []
+
def on_activate():
activations.append(True)
@@ -139,10 +128,13 @@ def on_activate():
def test_hotkeys(self):
q = queue.Queue()
- with GlobalHotKeys({
+ with GlobalHotKeys(
+ {
'++a': lambda: q.put('a'),
'++b': lambda: q.put('b'),
- '++c': lambda: q.put('c')}):
+ '++c': lambda: q.put('c'),
+ }
+ ):
notify('Press ++a')
self.assertEqual('a', q.get())
diff --git a/tests/keyboard_listener_tests.py b/tests/keyboard_listener_tests.py
index 3c95fbec..498daf9b 100644
--- a/tests/keyboard_listener_tests.py
+++ b/tests/keyboard_listener_tests.py
@@ -31,7 +31,8 @@
class KeyboardListenerTest(EventTest):
NOTIFICATION = (
'This test case is interactive, so you must follow the instructions '
- 'on screen')
+ 'on screen'
+ )
LISTENER_CLASS = pynput.keyboard.Listener
@contextlib.contextmanager
@@ -46,6 +47,7 @@ def events(self, timeout=5.0):
:param timeout: The number of seconds to wait for a key event.
"""
+
def generator(q):
while True:
try:
@@ -57,8 +59,9 @@ def generator(q):
# Yield the generator and allow the client to capture events
q = queue.Queue()
with self.listener(
- on_press=lambda k: q.put((k, True)),
- on_release=lambda k: q.put((k, False))):
+ on_press=lambda k: q.put((k, True)),
+ on_release=lambda k: q.put((k, False)),
+ ):
yield generator(q)
def assert_keys(self, failure_message, *args):
@@ -75,6 +78,7 @@ def assert_keys(self, failure_message, *args):
The tuple may only be a tuple of tuples, in which case any of the
values will be accepted.
"""
+
def normalize(event):
if not isinstance(event[0], tuple):
return normalize(((event[0],), event[1]))
@@ -83,10 +87,13 @@ def normalize(event):
tuple(
pynput.keyboard.KeyCode.from_char(key)
if isinstance(key, six.string_types)
- else key.value if key in pynput.keyboard.Key
+ else key.value
+ if key in pynput.keyboard.Key
else key
- for key in event[0]),
- is_pressed)
+ for key in event[0]
+ ),
+ is_pressed,
+ )
original_expected = [normalize(arg) for arg in args]
remaining = list(original_expected)
@@ -106,14 +113,14 @@ def normalize(event):
self.assertIn(
current[0][0],
expected[0],
- '%s was not found in %s' % (
- expected[0],
- current[0][0]))
+ '%s was not found in %s' % (expected[0], current[0][0]),
+ )
self.assertEqual(
current[1],
expected[1],
- 'Pressed state for %s was incorrect' % (
- str(current[0][0])))
+ 'Pressed state for %s was incorrect'
+ % (str(current[0][0])),
+ )
if not remaining:
break
@@ -122,10 +129,13 @@ def normalize(event):
self.assertSequenceEqual(
[],
remaining,
- '%s ([%s] != [%s])' % (
+ '%s ([%s] != [%s])'
+ % (
failure_message,
' '.join(str(e) for e in original_expected),
- ' '.join(str(a) for a in actual)))
+ ' '.join(str(a) for a in actual),
+ ),
+ )
finally:
self.notify('Press to continue...', delay=0)
@@ -145,35 +155,34 @@ def string_to_events(self, s):
def test_tap(self):
"""Tests that a single key can be tapped"""
self.notify('Press and release "a"')
- self.assert_keys(
- 'Failed to register event',
- ('a', True), ('a', False))
+ self.assert_keys('Failed to register event', ('a', True), ('a', False))
def test_enter(self):
"""Tests that the enter key can be tapped"""
self.notify('Press ')
self.assert_keys(
- 'Failed to register event',
- (pynput.keyboard.Key.enter, True))
+ 'Failed to register event', (pynput.keyboard.Key.enter, True)
+ )
def test_modifier(self):
"""Tests that the modifier keys can be tapped"""
from pynput.keyboard import Key
+
for key in (
- (Key.alt, Key.alt_l, Key.alt_r),
- (Key.ctrl, Key.ctrl_l, Key.ctrl_r),
- (Key.shift, Key.shift_l, Key.shift_r)):
+ (Key.alt, Key.alt_l, Key.alt_r),
+ (Key.ctrl, Key.ctrl_l, Key.ctrl_r),
+ (Key.shift, Key.shift_l, Key.shift_r),
+ ):
self.notify('Press <%s>' % key[0].name)
- self.assert_keys(
- 'Failed to register event',
- (key, True))
+ self.assert_keys('Failed to register event', (key, True))
def test_order(self):
"""Tests that the order of key events is correct"""
self.notify('Type "hello world"')
self.assert_keys(
'Failed to register event',
- *tuple(self.string_to_events('hello world')))
+ *tuple(self.string_to_events('hello world')),
+ )
def test_shift(self):
"""Tests that yields capital letters"""
@@ -184,13 +193,17 @@ def test_shift(self):
(
pynput.keyboard.Key.shift,
pynput.keyboard.Key.shift_l,
- pynput.keyboard.Key.shift_r),
- True),
- *tuple(self.string_to_events('TEST')))
+ pynput.keyboard.Key.shift_r,
+ ),
+ True,
+ ),
+ *tuple(self.string_to_events('TEST')),
+ )
def test_modifier_and_normal(self):
"""Tests that the modifier keys do not stick"""
from pynput.keyboard import Key
+
self.notify('Press a, , a')
self.assert_keys(
'Failed to register event',
@@ -199,7 +212,8 @@ def test_modifier_and_normal(self):
((Key.ctrl, Key.ctrl_l, Key.ctrl_r), True),
((Key.ctrl, Key.ctrl_l, Key.ctrl_r), False),
('a', True),
- ('a', False))
+ ('a', False),
+ )
def test_suppress(self):
"""Tests that passing ``suppress`` prevents events from propagating"""
@@ -209,18 +223,21 @@ def test_suppress(self):
'',
self.assert_keys(
'Failed to register event',
- *tuple(self.string_to_events('hello world'))).strip())
+ *tuple(self.string_to_events('hello world')),
+ ).strip(),
+ )
def test_reraise(self):
"""Tests that exception are reraised"""
- class MyException(Exception): pass
+
+ class MyException(Exception):
+ pass
def on_press(key):
raise MyException()
with self.assertRaises(MyException):
- with pynput.keyboard.Listener(
- on_press=on_press) as l:
+ with pynput.keyboard.Listener(on_press=on_press) as l:
self.notify('Press any key')
l.join()
@@ -229,46 +246,46 @@ def test_stop(self):
self.notify('Do not touch the keyboard')
with pynput.keyboard.Listener() as l:
+
def runner():
time.sleep(1)
l.stop()
threading.Thread(target=runner).start()
l.join(2.0)
- self.assertFalse(
- l.is_alive(),
- 'Listener did not stop')
+ self.assertFalse(l.is_alive(), 'Listener did not stop')
@darwin
def test_options_darwin(self):
"""Tests that options are correctly set on OSX"""
self.assertTrue(
pynput.keyboard.Listener(
- darwin_test=True,
- win32_test=False,
- xorg_test=False)._options['test'])
+ darwin_test=True, win32_test=False, xorg_test=False
+ )._options['test']
+ )
@win32
def test_options_win32(self):
"""Tests that options are correctly set on Windows"""
self.assertTrue(
pynput.keyboard.Listener(
- darwin_test=False,
- win32_test=True,
- xorg_test=False)._options['test'])
+ darwin_test=False, win32_test=True, xorg_test=False
+ )._options['test']
+ )
@xorg
def test_options_xorg(self):
"""Tests that options are correctly set on Linux"""
self.assertTrue(
pynput.keyboard.Listener(
- darwin_test=False,
- win32_test=False,
- xorg_test=True)._options['test'])
+ darwin_test=False, win32_test=False, xorg_test=True
+ )._options['test']
+ )
def test_events(self):
"""Tests that events are correctly yielded"""
from pynput.keyboard import Key, KeyCode, Events
+
self.notify('Press a, b, a, ')
with Events() as events:
@@ -288,7 +305,8 @@ def test_events(self):
Events.Release(KeyCode.from_char('b')),
Events.Press(KeyCode.from_char('a')),
Events.Release(KeyCode.from_char('a')),
- ])
+ ],
+ )
self.notify('Do not touch the keyboard', delay=2.0)
with Events() as events:
diff --git a/tests/mouse_controller_tests.py b/tests/mouse_controller_tests.py
index f5da5524..43df5f05 100644
--- a/tests/mouse_controller_tests.py
+++ b/tests/mouse_controller_tests.py
@@ -26,7 +26,8 @@ class MouseControllerTest(EventTest):
NOTIFICATION = (
'This test case is non-interactive, so you must not use the mouse.\n'
'You may need to keep the mouse pointer away from this window to '
- 'avoid interference.')
+ 'avoid interference.'
+ )
CONTROLLER_CLASS = pynput.mouse.Controller
LISTENER_CLASS = pynput.mouse.Listener
@@ -44,16 +45,19 @@ def assert_movement(self, failure_message, d):
self.assertEqual(
self.controller.position,
tuple(o + n for o, n in zip(pos, d)),
- failure_message)
+ failure_message,
+ )
def test_buttons(self):
"""Asserts that all buttons defined for the base mouse interface are
defined for the current platform"""
from pynput.mouse._base import Button
+
for button in Button:
self.assertTrue(
hasattr(pynput.mouse.Button, button.name),
- '%s is not defined for the current platform' % button.name)
+ '%s is not defined for the current platform' % button.name,
+ )
def test_position_get(self):
"""Tests that reading the position returns consistent values"""
@@ -61,12 +65,14 @@ def test_position_get(self):
self.assertTrue(
all(isinstance(i, numbers.Number) for i in position),
- 'Not all coordinates in %s are numbers' % str(position))
+ 'Not all coordinates in %s are numbers' % str(position),
+ )
self.assertEqual(
position,
self.controller.position,
- 'Second read of position returned different value')
+ 'Second read of position returned different value',
+ )
def test_position_set(self):
"""Tests that writing the position updates the position value"""
@@ -77,9 +83,8 @@ def test_position_set(self):
time.sleep(1)
self.assertEqual(
- new_position,
- self.controller.position,
- 'Updating position failed')
+ new_position, self.controller.position, 'Updating position failed'
+ )
def test_position_set_float(self):
"""Tests that writing a floating point position does not crash"""
@@ -90,99 +95,92 @@ def test_position_set_float(self):
def test_press(self):
"""Tests that press works"""
- for b in (
- pynput.mouse.Button.left,
- pynput.mouse.Button.right):
+ for b in (pynput.mouse.Button.left, pynput.mouse.Button.right):
with self.assert_event(
- 'Failed to send press event',
- on_click=lambda x, y, button, pressed:
- button == b and pressed):
+ 'Failed to send press event',
+ on_click=lambda x, y, button, pressed, injected: (
+ button == b and pressed
+ ),
+ ):
self.controller.press(b)
self.controller.release(b)
def test_release(self):
"""Tests that release works"""
- for b in (
- pynput.mouse.Button.left,
- pynput.mouse.Button.right):
+ for b in (pynput.mouse.Button.left, pynput.mouse.Button.right):
self.controller.press(b)
with self.assert_event(
- 'Failed to send release event',
- on_click=lambda x, y, button, pressed:
- button == b and not pressed):
+ 'Failed to send release event',
+ on_click=lambda x, y, button, pressed, injected: (
+ button == b and not pressed
+ ),
+ ):
self.controller.release(b)
def test_left(self):
"""Tests that moving left works"""
ox, oy = self.controller.position
with self.assert_event(
- 'Failed to send move left event',
- on_move=lambda x, y: x < ox):
- self.assert_movement(
- 'Pointer did not move',
- (-1, 0))
+ 'Failed to send move left event',
+ on_move=lambda x, y, injected: x < ox,
+ ):
+ self.assert_movement('Pointer did not move', (-1, 0))
def test_right(self):
"""Tests that moving right works"""
ox, oy = self.controller.position
with self.assert_event(
- 'Failed to send move right event',
- on_move=lambda x, y: x > ox):
- self.assert_movement(
- 'Pointer did not move',
- (1, 0))
+ 'Failed to send move right event',
+ on_move=lambda x, y, injected: x > ox,
+ ):
+ self.assert_movement('Pointer did not move', (1, 0))
def test_up(self):
"""Tests that moving up works"""
ox, oy = self.controller.position
with self.assert_event(
- 'Failed to send move up event',
- on_move=lambda x, y: y < oy):
- self.assert_movement(
- 'Pointer did not move',
- (0, -1))
+ 'Failed to send move up event',
+ on_move=lambda x, y, injected: y < oy,
+ ):
+ self.assert_movement('Pointer did not move', (0, -1))
def test_down(self):
"""Tests that moving down works"""
ox, oy = self.controller.position
with self.assert_event(
- 'Failed to send move down event',
- on_move=lambda x, y: y > oy):
- self.assert_movement(
- 'Pointer did not move',
- (0, 1))
+ 'Failed to send move down event',
+ on_move=lambda x, y, injected: y > oy,
+ ):
+ self.assert_movement('Pointer did not move', (0, 1))
def test_click(self):
"""Tests that click works"""
- for b in (
- pynput.mouse.Button.left,
- pynput.mouse.Button.right):
+ for b in (pynput.mouse.Button.left, pynput.mouse.Button.right):
events = [True, False]
events.reverse()
- def on_click(x, y, button, pressed):
+ def on_click(x, y, button, pressed, injected):
if button == b:
- self.assertEqual(
- pressed,
- events.pop(),
- 'Unexpected event')
+ self.assertEqual(pressed, events.pop(), 'Unexpected event')
return len(events) == 0
with self.assert_event(
- 'Failed to send click events',
- on_click=on_click):
+ 'Failed to send click events', on_click=on_click
+ ):
self.controller.click(b)
def test_scroll_up(self):
"""Tests that scrolling up works"""
with self.assert_event(
- 'Failed to send scroll up event',
- on_scroll=lambda x, y, dx, dy: dy > 0):
+ 'Failed to send scroll up event',
+ on_scroll=lambda x, y, dx, dy, injected: dy > 0,
+ ):
self.controller.scroll(0, 1)
def test_scroll_down(self):
"""Tests that scrolling down works"""
with self.assert_event(
- 'Failed to send scroll down event',
- on_scroll=lambda x, y, dx, dy: dy < 0):
+ 'Failed to send scroll down event',
+ on_scroll=lambda x, y, dx, dy, injected: dy < 0,
+ ):
self.controller.scroll(0, -1)
diff --git a/tests/mouse_listener_tests.py b/tests/mouse_listener_tests.py
index e91f970a..d2661859 100644
--- a/tests/mouse_listener_tests.py
+++ b/tests/mouse_listener_tests.py
@@ -25,7 +25,8 @@ class MouseListenerTest(EventTest):
NOTIFICATION = (
'This test case is interactive, so you must follow the instructions '
'on screen.\n'
- 'You do not have to perform any actions on this specific window.')
+ 'You do not have to perform any actions on this specific window.'
+ )
LISTENER_CLASS = pynput.mouse.Listener
def test_stop(self):
@@ -54,91 +55,99 @@ def test_move(self):
"""Tests that move events are emitted at all"""
self.notify('Move mouse pointer')
self.assert_cumulative(
- 'Failed to register movement',
- on_move=lambda a, b: True)
+ 'Failed to register movement', on_move=lambda a, b: True
+ )
def test_left(self):
"""Tests that move left events are emitted correctly"""
self.notify('Move mouse pointer left')
self.assert_cumulative(
- 'Failed to register movement',
- on_move=lambda a, b: b[0] < a[0])
+ 'Failed to register movement', on_move=lambda a, b: b[0] < a[0]
+ )
def test_right(self):
"""Tests that move right events are emitted correctly"""
self.notify('Move mouse pointer right')
self.assert_cumulative(
- 'Failed to register movement',
- on_move=lambda a, b: b[0] > a[0])
+ 'Failed to register movement', on_move=lambda a, b: b[0] > a[0]
+ )
def test_up(self):
"""Tests that move up events are emitted correctly"""
self.notify('Move mouse pointer up')
self.assert_cumulative(
- 'Failed to register movement',
- on_move=lambda a, b: b[1] < a[1])
+ 'Failed to register movement', on_move=lambda a, b: b[1] < a[1]
+ )
def test_down(self):
"""Tests that move down events are emitted correctly"""
self.notify('Move mouse pointer down')
self.assert_cumulative(
- 'Failed to register movement',
- on_move=lambda a, b: b[1] > a[1])
+ 'Failed to register movement', on_move=lambda a, b: b[1] > a[1]
+ )
def test_click_left(self):
"""Tests that left click events are emitted"""
self.notify('Click left mouse button')
self.assert_stop(
'No left click registered',
- on_click=lambda x, y, button, pressed: not (
- pressed and button == pynput.mouse.Button.left))
+ on_click=lambda x, y, button, pressed: (
+ not (pressed and button == pynput.mouse.Button.left)
+ ),
+ )
def test_click_right(self):
"""Tests that right click events are emitted"""
self.notify('Click right mouse button')
self.assert_stop(
'No right click registered',
- on_click=lambda x, y, button, pressed: not (
- pressed and button == pynput.mouse.Button.right))
+ on_click=lambda x, y, button, pressed: (
+ not (pressed and button == pynput.mouse.Button.right)
+ ),
+ )
def test_scroll_up(self):
"""Tests that scroll up events are emitted"""
self.notify('Scroll up')
self.assert_stop(
'No scroll up registered',
- on_scroll=lambda x, y, dx, dy: not (
- dy > 0))
+ on_scroll=lambda x, y, dx, dy: not (dy > 0),
+ )
def test_scroll_down(self):
"""Tests that scroll down events are emitted"""
self.notify('Scroll down')
self.assert_stop(
'No scroll down registered',
- on_scroll=lambda x, y, dx, dy: not (
- dy < 0))
+ on_scroll=lambda x, y, dx, dy: not (dy < 0),
+ )
def test_suppress(self):
"""Tests that passing ``suppress`` prevents events from propagating"""
self.suppress = True
self.notify(
'Click right mouse button where it will have an effect, and then '
- 'press the left mouse button')
+ 'press the left mouse button'
+ )
self.assert_stop(
'No right click registered',
- on_click=lambda x, y, button, pressed: not (
- pressed and button == pynput.mouse.Button.left))
+ on_click=lambda x, y, button, pressed: (
+ not (pressed and button == pynput.mouse.Button.left)
+ ),
+ )
self.confirm('Was the action suppressed?')
def test_reraise(self):
"""Tests that exception are reraised"""
- class MyException(Exception): pass
+
+ class MyException(Exception):
+ pass
def on_click(x, y, button, pressed):
raise MyException()
with self.assertRaises(MyException):
- with pynput.mouse.Listener(
- on_click=on_click) as l:
+ with pynput.mouse.Listener(on_click=on_click) as l:
self.notify('Click any button')
l.join()
@@ -147,31 +156,32 @@ def test_options_darwin(self):
"""Tests that options are correctly set on OSX"""
self.assertTrue(
pynput.mouse.Listener(
- darwin_test=True,
- win32_test=False,
- xorg_test=False)._options['test'])
+ darwin_test=True, win32_test=False, xorg_test=False
+ )._options['test']
+ )
@win32
def test_options_win32(self):
"""Tests that options are correctly set on Windows"""
self.assertTrue(
pynput.mouse.Listener(
- darwin_test=False,
- win32_test=True,
- xorg_test=False)._options['test'])
+ darwin_test=False, win32_test=True, xorg_test=False
+ )._options['test']
+ )
@xorg
def test_options_xorg(self):
"""Tests that options are correctly set on Linux"""
self.assertTrue(
pynput.mouse.Listener(
- darwin_test=False,
- win32_test=False,
- xorg_test=True)._options['test'])
+ darwin_test=False, win32_test=False, xorg_test=True
+ )._options['test']
+ )
def test_events(self):
"""Tests that events are correctly yielded"""
from pynput.mouse import Button, Events
+
with Events() as events:
self.notify('Move the mouse')
for event in events:
@@ -180,14 +190,18 @@ def test_events(self):
self.notify('Press the left mouse button')
for event in events:
- if isinstance(event, Events.Click) \
- and event.button == Button.left:
+ if (
+ isinstance(event, Events.Click)
+ and event.button == Button.left
+ ):
break
self.notify('Press the right mouse button')
for event in events:
- if isinstance(event, Events.Click) \
- and event.button == Button.right:
+ if (
+ isinstance(event, Events.Click)
+ and event.button == Button.right
+ ):
break
self.notify('Scroll the mouse')