From 03ccb935fec88f10cd99f0b2e6f8bfee41e06ab9 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Sun, 27 Dec 2020 19:06:17 +0100 Subject: [PATCH] horizontal joystick scrolling --- keymapper/config.py | 2 + keymapper/dev/ev_abs_mapper.py | 28 +++++++------ keymapper/dev/injector.py | 6 +-- keymapper/dev/permissions.py | 1 - keymapper/mapping.py | 1 - tests/test.py | 5 +++ tests/testcases/test_ev_abs_mapper.py | 59 ++++++++++++--------------- tests/testcases/test_injector.py | 5 +++ 8 files changed, 56 insertions(+), 51 deletions(-) diff --git a/keymapper/config.py b/keymapper/config.py index 3dce3121..94eae4fb 100644 --- a/keymapper/config.py +++ b/keymapper/config.py @@ -51,6 +51,8 @@ INITIAL_CONFIG = { 'pointer_speed': 80, 'left_purpose': MOUSE, 'right_purpose': WHEEL, + 'x_scroll_speed': 2, + 'y_scroll_speed': 0.5 }, } } diff --git a/keymapper/dev/ev_abs_mapper.py b/keymapper/dev/ev_abs_mapper.py index f15b911b..bac3f762 100644 --- a/keymapper/dev/ev_abs_mapper.py +++ b/keymapper/dev/ev_abs_mapper.py @@ -41,7 +41,7 @@ JOYSTICK = [ ] # miniscule movements on the joystick should not trigger a mouse wheel event -WHEEL_THRESHOLD = 0.3 +WHEEL_THRESHOLD = 0.15 def _write(device, ev_type, keycode, value): @@ -146,6 +146,8 @@ async def ev_abs_mapper(abs_state, input_device, keymapper_device, mapping): non_linearity = mapping.get('gamepad.joystick.non_linearity') left_purpose = mapping.get('gamepad.joystick.left_purpose') right_purpose = mapping.get('gamepad.joystick.right_purpose') + x_scroll_speed = mapping.get('gamepad.joystick.x_scroll_speed') + y_scroll_speed = mapping.get('gamepad.joystick.y_scroll_speed') logger.info( 'Left joystick as %s, right joystick as %s', @@ -161,15 +163,15 @@ async def ev_abs_mapper(abs_state, input_device, keymapper_device, mapping): right_purpose ) - if non_linearity != 1: - # to make small movements smaller for more precision - speed = (mouse_x ** 2 + mouse_y ** 2) ** 0.5 - factor = (speed / max_speed) ** non_linearity - else: - factor = 1 - # mouse movements if abs(mouse_x) > 0 or abs(mouse_y) > 0: + if non_linearity != 1: + # to make small movements smaller for more precision + speed = (mouse_x ** 2 + mouse_y ** 2) ** 0.5 + factor = (speed / max_speed) ** non_linearity + else: + factor = 1 + rel_x = mouse_x * factor * pointer_speed / max_value rel_y = mouse_y * factor * pointer_speed / max_value pending_x_rel, rel_x = accumulate(pending_x_rel, rel_x) @@ -181,15 +183,15 @@ async def ev_abs_mapper(abs_state, input_device, keymapper_device, mapping): # wheel movements if abs(wheel_x) > 0: - float_rel_rx = wheel_x / max_value + float_rel_rx = wheel_x * x_scroll_speed / max_value pending_rx_rel, rel_rx = accumulate(pending_rx_rel, float_rel_rx) - if abs(float_rel_rx) > WHEEL_THRESHOLD: - _write(keymapper_device, EV_REL, REL_HWHEEL, -rel_rx) + if abs(float_rel_rx) > WHEEL_THRESHOLD * x_scroll_speed: + _write(keymapper_device, EV_REL, REL_HWHEEL, rel_rx) if abs(wheel_y) > 0: - float_rel_ry = wheel_y / max_value + float_rel_ry = wheel_y * y_scroll_speed / max_value pending_ry_rel, rel_ry = accumulate(pending_ry_rel, float_rel_ry) - if abs(float_rel_ry) > WHEEL_THRESHOLD: + if abs(float_rel_ry) > WHEEL_THRESHOLD * y_scroll_speed: _write(keymapper_device, EV_REL, REL_WHEEL, -rel_ry) # try to do this as close to 60hz as possible diff --git a/keymapper/dev/injector.py b/keymapper/dev/injector.py index 5f909c7b..9fc450f9 100644 --- a/keymapper/dev/injector.py +++ b/keymapper/dev/injector.py @@ -247,13 +247,13 @@ class KeycodeInjector: capabilities[EV_KEY] += list(macro.get_capabilities()) if abs_to_rel: - # those are the requirements to recognize it as mouse - # on my system. REL_X and REL_Y are of course required to - # accept the events that the mouse-movement-mapper writes. + # REL_WHEEL was also required to recognize the gamepad + # as mouse, even if no joystick is used as wheel. capabilities[EV_REL] = [ evdev.ecodes.REL_X, evdev.ecodes.REL_Y, evdev.ecodes.REL_WHEEL, + evdev.ecodes.REL_HWHEEL, ] keys = capabilities.get(EV_KEY) if keys is None: diff --git a/keymapper/dev/permissions.py b/keymapper/dev/permissions.py index ffa2aa9a..7af81085 100644 --- a/keymapper/dev/permissions.py +++ b/keymapper/dev/permissions.py @@ -97,7 +97,6 @@ def can_read_devices(): plugdev_check = check_group('plugdev') # ubuntu. funnily, individual devices in /dev/input/ have write permitted. - print(is_service_running(), check_injection_rights()) if not is_service_running(): can_write = check_injection_rights() else: diff --git a/keymapper/mapping.py b/keymapper/mapping.py index 9a28d0bb..6dfb5f01 100644 --- a/keymapper/mapping.py +++ b/keymapper/mapping.py @@ -56,7 +56,6 @@ class Mapping(ConfigBase): def set(self, *args): """Set a config value. See `ConfigBase.set`.""" - print('set', args) self.changed = True return super().set(*args) diff --git a/tests/test.py b/tests/test.py index 45c296a6..f2081ec9 100644 --- a/tests/test.py +++ b/tests/test.py @@ -365,6 +365,9 @@ _fixture_copy = copy.deepcopy(fixtures) def cleanup(): """Reset the applications state.""" + for task in asyncio.Task.all_tasks(): + task.cancel() + os.system('pkill -f key-mapper-service') if os.path.exists(tmp): shutil.rmtree(tmp) @@ -374,6 +377,7 @@ def cleanup(): system_mapping.populate() custom_mapping.empty() + custom_mapping.clear_config() clear_write_history() @@ -388,6 +392,7 @@ def cleanup(): for path in list(_fixture_copy.keys()): if path not in fixtures: fixtures[path] = _fixture_copy[path] + refresh_devices() diff --git a/tests/testcases/test_ev_abs_mapper.py b/tests/testcases/test_ev_abs_mapper.py index c859a137..d950b3c8 100644 --- a/tests/testcases/test_ev_abs_mapper.py +++ b/tests/testcases/test_ev_abs_mapper.py @@ -30,7 +30,7 @@ from keymapper.mapping import Mapping from keymapper.dev.ev_abs_mapper import MOUSE, WHEEL from tests.test import InputDevice, UInput, MAX_ABS, clear_write_history, \ - uinput_write_history + uinput_write_history, cleanup abs_state = [0, 0, 0, 0] @@ -53,21 +53,11 @@ class TestEvAbsMapper(unittest.TestCase): self.mapping )) + config.set('gamepad.joystick.x_scroll_speed', 1) + config.set('gamepad.joystick.y_scroll_speed', 1) + def tearDown(self): - config.clear_config() - self.mapping.clear_config() - loop = asyncio.get_event_loop() - - try: - for task in asyncio.Task.all_tasks(): - task.cancel() - - loop.stop() - loop.close() - except RuntimeError: - pass - - clear_write_history() + cleanup() def do(self, a, b, c, d, expectation): """Present fake values to the loop and observe the outcome.""" @@ -97,9 +87,9 @@ class TestEvAbsMapper(unittest.TestCase): self.do(0, MAX_ABS, 0, 0, (EV_REL, REL_Y, speed)) self.do(0, -MAX_ABS, 0, 0, (EV_REL, REL_Y, -speed)) - # wheel event values are negative - self.do(0, 0, MAX_ABS, 0, (EV_REL, REL_HWHEEL, -1)) - self.do(0, 0, -MAX_ABS, 0, (EV_REL, REL_HWHEEL, 1)) + # vertical wheel event values are negative + self.do(0, 0, MAX_ABS, 0, (EV_REL, REL_HWHEEL, 1)) + self.do(0, 0, -MAX_ABS, 0, (EV_REL, REL_HWHEEL, -1)) self.do(0, 0, 0, MAX_ABS, (EV_REL, REL_WHEEL, -1)) self.do(0, 0, 0, -MAX_ABS, (EV_REL, REL_WHEEL, 1)) @@ -109,13 +99,15 @@ class TestEvAbsMapper(unittest.TestCase): config.set('gamepad.joystick.pointer_speed', speed) config.set('gamepad.joystick.left_purpose', WHEEL) config.set('gamepad.joystick.right_purpose', MOUSE) + config.set('gamepad.joystick.x_scroll_speed', 1) + config.set('gamepad.joystick.y_scroll_speed', 2) - self.do(MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, -1)) - self.do(-MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, 1)) - self.do(0, MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, -1)) - self.do(0, -MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, 1)) + # vertical wheel event values are negative + self.do(MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, 1)) + self.do(-MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, -1)) + self.do(0, MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, -2)) + self.do(0, -MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, 2)) - # wheel event values are negative self.do(0, 0, MAX_ABS, 0, (EV_REL, REL_X, speed)) self.do(0, 0, -MAX_ABS, 0, (EV_REL, REL_X, -speed)) self.do(0, 0, 0, MAX_ABS, (EV_REL, REL_Y, speed)) @@ -133,7 +125,6 @@ class TestEvAbsMapper(unittest.TestCase): self.do(0, MAX_ABS, 0, 0, (EV_REL, REL_Y, speed)) self.do(0, -MAX_ABS, 0, 0, (EV_REL, REL_Y, -speed)) - # wheel event values are negative self.do(0, 0, MAX_ABS, 0, (EV_REL, REL_X, speed)) self.do(0, 0, -MAX_ABS, 0, (EV_REL, REL_X, -speed)) self.do(0, 0, 0, MAX_ABS, (EV_REL, REL_Y, speed)) @@ -142,17 +133,19 @@ class TestEvAbsMapper(unittest.TestCase): def test_joystick_purpose_4(self): config.set('gamepad.joystick.left_purpose', WHEEL) config.set('gamepad.joystick.right_purpose', WHEEL) + self.mapping.set('gamepad.joystick.x_scroll_speed', 2) + self.mapping.set('gamepad.joystick.y_scroll_speed', 3) - self.do(MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, -1)) - self.do(-MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, 1)) - self.do(0, MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, -1)) - self.do(0, -MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, 1)) + self.do(MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, 2)) + self.do(-MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, -2)) + self.do(0, MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, -3)) + self.do(0, -MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, 3)) - # wheel event values are negative - self.do(0, 0, MAX_ABS, 0, (EV_REL, REL_HWHEEL, -1)) - self.do(0, 0, -MAX_ABS, 0, (EV_REL, REL_HWHEEL, 1)) - self.do(0, 0, 0, MAX_ABS, (EV_REL, REL_WHEEL, -1)) - self.do(0, 0, 0, -MAX_ABS, (EV_REL, REL_WHEEL, 1)) + # vertical wheel event values are negative + self.do(0, 0, MAX_ABS, 0, (EV_REL, REL_HWHEEL, 2)) + self.do(0, 0, -MAX_ABS, 0, (EV_REL, REL_HWHEEL, -2)) + self.do(0, 0, 0, MAX_ABS, (EV_REL, REL_WHEEL, -3)) + self.do(0, 0, 0, -MAX_ABS, (EV_REL, REL_WHEEL, 3)) if __name__ == "__main__": diff --git a/tests/testcases/test_injector.py b/tests/testcases/test_injector.py index fd4e3ad5..e6026c6e 100644 --- a/tests/testcases/test_injector.py +++ b/tests/testcases/test_injector.py @@ -174,6 +174,11 @@ class TestInjector(unittest.TestCase): self.assertNotIn(evdev.ecodes.EV_ABS, capabilities) self.assertIn(evdev.ecodes.EV_REL, capabilities) + self.assertIn(evdev.ecodes.REL_X, capabilities.get(EV_REL)) + self.assertIn(evdev.ecodes.REL_Y, capabilities.get(EV_REL)) + self.assertIn(evdev.ecodes.REL_WHEEL, capabilities.get(EV_REL)) + self.assertIn(evdev.ecodes.REL_HWHEEL, capabilities.get(EV_REL)) + self.assertIn(evdev.ecodes.EV_KEY, capabilities) self.assertEqual(len(capabilities[evdev.ecodes.EV_KEY]), 1)