horizontal joystick scrolling

This commit is contained in:
sezanzeb 2020-12-27 19:06:17 +01:00
parent 4a745eb2e9
commit 03ccb935fe
8 changed files with 56 additions and 51 deletions

View File

@ -51,6 +51,8 @@ INITIAL_CONFIG = {
'pointer_speed': 80, 'pointer_speed': 80,
'left_purpose': MOUSE, 'left_purpose': MOUSE,
'right_purpose': WHEEL, 'right_purpose': WHEEL,
'x_scroll_speed': 2,
'y_scroll_speed': 0.5
}, },
} }
} }

View File

@ -41,7 +41,7 @@ JOYSTICK = [
] ]
# miniscule movements on the joystick should not trigger a mouse wheel event # 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): 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') non_linearity = mapping.get('gamepad.joystick.non_linearity')
left_purpose = mapping.get('gamepad.joystick.left_purpose') left_purpose = mapping.get('gamepad.joystick.left_purpose')
right_purpose = mapping.get('gamepad.joystick.right_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( logger.info(
'Left joystick as %s, right joystick as %s', 'Left joystick as %s, right joystick as %s',
@ -161,6 +163,8 @@ async def ev_abs_mapper(abs_state, input_device, keymapper_device, mapping):
right_purpose right_purpose
) )
# mouse movements
if abs(mouse_x) > 0 or abs(mouse_y) > 0:
if non_linearity != 1: if non_linearity != 1:
# to make small movements smaller for more precision # to make small movements smaller for more precision
speed = (mouse_x ** 2 + mouse_y ** 2) ** 0.5 speed = (mouse_x ** 2 + mouse_y ** 2) ** 0.5
@ -168,8 +172,6 @@ async def ev_abs_mapper(abs_state, input_device, keymapper_device, mapping):
else: else:
factor = 1 factor = 1
# mouse movements
if abs(mouse_x) > 0 or abs(mouse_y) > 0:
rel_x = mouse_x * factor * pointer_speed / max_value rel_x = mouse_x * factor * pointer_speed / max_value
rel_y = mouse_y * 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) 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 # wheel movements
if abs(wheel_x) > 0: 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) pending_rx_rel, rel_rx = accumulate(pending_rx_rel, float_rel_rx)
if abs(float_rel_rx) > WHEEL_THRESHOLD: if abs(float_rel_rx) > WHEEL_THRESHOLD * x_scroll_speed:
_write(keymapper_device, EV_REL, REL_HWHEEL, -rel_rx) _write(keymapper_device, EV_REL, REL_HWHEEL, rel_rx)
if abs(wheel_y) > 0: 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) 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) _write(keymapper_device, EV_REL, REL_WHEEL, -rel_ry)
# try to do this as close to 60hz as possible # try to do this as close to 60hz as possible

View File

@ -247,13 +247,13 @@ class KeycodeInjector:
capabilities[EV_KEY] += list(macro.get_capabilities()) capabilities[EV_KEY] += list(macro.get_capabilities())
if abs_to_rel: if abs_to_rel:
# those are the requirements to recognize it as mouse # REL_WHEEL was also required to recognize the gamepad
# on my system. REL_X and REL_Y are of course required to # as mouse, even if no joystick is used as wheel.
# accept the events that the mouse-movement-mapper writes.
capabilities[EV_REL] = [ capabilities[EV_REL] = [
evdev.ecodes.REL_X, evdev.ecodes.REL_X,
evdev.ecodes.REL_Y, evdev.ecodes.REL_Y,
evdev.ecodes.REL_WHEEL, evdev.ecodes.REL_WHEEL,
evdev.ecodes.REL_HWHEEL,
] ]
keys = capabilities.get(EV_KEY) keys = capabilities.get(EV_KEY)
if keys is None: if keys is None:

View File

@ -97,7 +97,6 @@ def can_read_devices():
plugdev_check = check_group('plugdev') plugdev_check = check_group('plugdev')
# ubuntu. funnily, individual devices in /dev/input/ have write permitted. # ubuntu. funnily, individual devices in /dev/input/ have write permitted.
print(is_service_running(), check_injection_rights())
if not is_service_running(): if not is_service_running():
can_write = check_injection_rights() can_write = check_injection_rights()
else: else:

View File

@ -56,7 +56,6 @@ class Mapping(ConfigBase):
def set(self, *args): def set(self, *args):
"""Set a config value. See `ConfigBase.set`.""" """Set a config value. See `ConfigBase.set`."""
print('set', args)
self.changed = True self.changed = True
return super().set(*args) return super().set(*args)

View File

@ -365,6 +365,9 @@ _fixture_copy = copy.deepcopy(fixtures)
def cleanup(): def cleanup():
"""Reset the applications state.""" """Reset the applications state."""
for task in asyncio.Task.all_tasks():
task.cancel()
os.system('pkill -f key-mapper-service') os.system('pkill -f key-mapper-service')
if os.path.exists(tmp): if os.path.exists(tmp):
shutil.rmtree(tmp) shutil.rmtree(tmp)
@ -374,6 +377,7 @@ def cleanup():
system_mapping.populate() system_mapping.populate()
custom_mapping.empty() custom_mapping.empty()
custom_mapping.clear_config()
clear_write_history() clear_write_history()
@ -388,6 +392,7 @@ def cleanup():
for path in list(_fixture_copy.keys()): for path in list(_fixture_copy.keys()):
if path not in fixtures: if path not in fixtures:
fixtures[path] = _fixture_copy[path] fixtures[path] = _fixture_copy[path]
refresh_devices() refresh_devices()

View File

@ -30,7 +30,7 @@ from keymapper.mapping import Mapping
from keymapper.dev.ev_abs_mapper import MOUSE, WHEEL from keymapper.dev.ev_abs_mapper import MOUSE, WHEEL
from tests.test import InputDevice, UInput, MAX_ABS, clear_write_history, \ from tests.test import InputDevice, UInput, MAX_ABS, clear_write_history, \
uinput_write_history uinput_write_history, cleanup
abs_state = [0, 0, 0, 0] abs_state = [0, 0, 0, 0]
@ -53,21 +53,11 @@ class TestEvAbsMapper(unittest.TestCase):
self.mapping self.mapping
)) ))
config.set('gamepad.joystick.x_scroll_speed', 1)
config.set('gamepad.joystick.y_scroll_speed', 1)
def tearDown(self): def tearDown(self):
config.clear_config() cleanup()
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()
def do(self, a, b, c, d, expectation): def do(self, a, b, c, d, expectation):
"""Present fake values to the loop and observe the outcome.""" """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))
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 # 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, -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))
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.pointer_speed', speed)
config.set('gamepad.joystick.left_purpose', WHEEL) config.set('gamepad.joystick.left_purpose', WHEEL)
config.set('gamepad.joystick.right_purpose', MOUSE) 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)) # 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, -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, -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, -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)) 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))
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, -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)) 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): def test_joystick_purpose_4(self):
config.set('gamepad.joystick.left_purpose', WHEEL) config.set('gamepad.joystick.left_purpose', WHEEL)
config.set('gamepad.joystick.right_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, 2))
self.do(-MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, 1)) self.do(-MAX_ABS, 0, 0, 0, (EV_REL, REL_HWHEEL, -2))
self.do(0, MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, -1)) self.do(0, MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, -3))
self.do(0, -MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, 1)) self.do(0, -MAX_ABS, 0, 0, (EV_REL, REL_WHEEL, 3))
# wheel event values are negative # 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, 2))
self.do(0, 0, -MAX_ABS, 0, (EV_REL, REL_HWHEEL, 1)) self.do(0, 0, -MAX_ABS, 0, (EV_REL, REL_HWHEEL, -2))
self.do(0, 0, 0, MAX_ABS, (EV_REL, REL_WHEEL, -1)) self.do(0, 0, 0, MAX_ABS, (EV_REL, REL_WHEEL, -3))
self.do(0, 0, 0, -MAX_ABS, (EV_REL, REL_WHEEL, 1)) self.do(0, 0, 0, -MAX_ABS, (EV_REL, REL_WHEEL, 3))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -174,6 +174,11 @@ class TestInjector(unittest.TestCase):
self.assertNotIn(evdev.ecodes.EV_ABS, capabilities) self.assertNotIn(evdev.ecodes.EV_ABS, capabilities)
self.assertIn(evdev.ecodes.EV_REL, 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.assertIn(evdev.ecodes.EV_KEY, capabilities)
self.assertEqual(len(capabilities[evdev.ecodes.EV_KEY]), 1) self.assertEqual(len(capabilities[evdev.ecodes.EV_KEY]), 1)