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')