using python-evdev

first
sezanzeb 4 years ago
parent dcf9462321
commit 5e80afc7ce

@ -18,7 +18,9 @@ sudo python3 setup.py install && sudo key-mapper-gtk -d
# Dependencies
`evtest`, `libinput`
No idea which one are relevant at the moment
`evtest`, `libinput`, `python-evdev`
# Tests

@ -32,10 +32,10 @@ gi.require_version('GLib', '2.0')
from gi.repository import Gtk
from keymapper.data import get_data_path
from keymapper.X import find_devices, create_setxkbmap_config, \
from keymapper.X import create_setxkbmap_config, \
create_identity_mapping
from keymapper.presets import get_presets, get_mappings, \
find_newest_preset, create_preset
find_newest_preset, create_preset, get_devices
from keymapper.logger import logger, update_verbosity, log_info
@ -115,7 +115,7 @@ class Window:
def populate_devices(self):
"""Make the devices selectable."""
devices = find_devices()
devices = get_devices()
device_selection = self.get('device_selection')
for device in devices:
device_selection.append(device, device)

@ -159,113 +159,7 @@ def generate_symbols_file_content(device, preset, mappings):
return result
def parse_libinput_list():
# TODO return something meaningful. {name: {paths:, related:}}
"""Get a mapping of {name: [paths]} for `libinput list-devices` devices.
This is grouped by group, so the "Logitech USB Keyboard" and
"Logitech USB Keyboard Consumer Control" are one key (the shorter one),
and the paths array for that is therefore 2 entries large.
"""
stdout = subprocess.check_output(['libinput', 'list-devices'])
devices = [
device for device in stdout.decode().split('\n\n')
if device != ''
]
grouped = {}
for device in devices:
info = {}
for line in device.split('\n'):
# example:
# "Kernel: /dev/input/event0"
match = re.match(r'(\w+):\s+(.+)', line)
if match is None:
continue
info[match[1]] = match[2]
name = info['Device']
group = info['Group'] # int
dev = info['Kernel'] # /dev/input/event#
if grouped.get(group) is None:
grouped[group] = []
grouped[group].append((name, dev))
result = {}
for i in grouped:
group = grouped[i]
names = [entry[0] for entry in group]
devs = [entry[1] for entry in group]
shortest_name = sorted(names, key=len)[0]
result[shortest_name] = devs
return result
def parse_evtest():
"""Get a mapping of {name: [paths]} for each evtest device.
This is grouped by name, so "Logitech USB Keyboard" and
"Logitech USB Keyboard Consumer Control" are two keys in result. Some
devices have the same name for each of those entries.
Use parse_libinput_list instead, which properly groups all of them.
"""
# It asks for a device afterwads, so just insert garbage into it
p = subprocess.Popen(
'echo a | sudo evtest',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# the list we are looking for is in stderr
_, evtest = p.communicate()
evtest = [
line
for line in evtest.decode().split('\n')
if line.startswith('/dev')
]
logger.debug('evtest devices: \n%s', '\n'.join(evtest))
# evtest also returns a bunch of other devices, like some audio devices,
# so check this list against `xinput list` to get keyboards and mice
xinput = get_xinput_list()
logger.debug('xinput devices: \n%s', '\n'.join(xinput))
result = {}
for line in evtest:
match = re.search(r'(/dev/input/event\d+):\s+(.+)', line)
if match is None:
continue
# the path refers to a file in /dev/input/event#. Note, that this is
# different from the id that `xinput list` can return.
path = match[1]
name = match[2]
if name not in xinput:
continue
if not result.get(name):
result[name] = []
result[name].append(path)
return result
def get_xinput_list():
"""Run xinput and get the resulting device names as list."""
xinput = subprocess.check_output(['xinput', 'list', f'--name-only'])
return [line for line in xinput.decode().split('\n') if line != '']
_devices = None
def find_devices():
"""Return a mapping of {name: [paths]} for each input device."""
global _devices
# this is expensive, do it only once
if _devices is None:
_devices = parse_libinput_list()
logger.info('Found %s', ', '.join([f'"{name}"' for name in _devices]))
return _devices

@ -24,10 +24,43 @@
import os
import glob
import evdev
from keymapper.paths import CONFIG_PATH
from keymapper.logger import logger
from keymapper.X import find_devices, create_setxkbmap_config
from keymapper.X import create_setxkbmap_config
_devices = None
def get_devices():
"""Get a mapping of {name: [paths]} for input devices."""
# cache the result, this takes a second to complete
global _devices
if _devices is not None:
return _devices
devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
# group them together by usb device because there could be stuff like
# "Logitech USB Keyboard" and "Logitech USB Keyboard Consumer Control"
grouped = {}
for device in devices:
usb = device.phys.split('/')[0]
if grouped.get(usb) is None:
grouped[usb] = []
grouped[usb].append((device.name, device.path))
# now write down all the paths of that group
result = {}
for group in grouped.values():
names = [entry[0] for entry in group]
devs = [entry[1] for entry in group]
shortest_name = sorted(names, key=len)[0]
result[shortest_name] = devs
_devices = result
logger.info('Found %s', ', '.join([f'"{name}"' for name in result]))
return result
def get_presets(device):
@ -82,7 +115,7 @@ def get_mappings(device, preset):
def get_any_preset():
"""Return the first found tuple of (device, preset)."""
any_device = list(find_devices().keys())[0]
any_device = list(get_devices().keys())[0]
any_preset = (get_presets(any_device) or [None])[0]
return any_device, any_preset
@ -102,7 +135,7 @@ def find_newest_preset():
logger.debug('No presets found.')
return get_any_preset()
online_devices = find_devices().keys()
online_devices = get_devices().keys()
newest_path = None
while len(paths) > 0:

@ -30,8 +30,8 @@ from keymapper import paths
paths.SYMBOLS_PATH = '/tmp/key-mapper-test/symbols'
paths.CONFIG_PATH = '/tmp/key-mapper-test/.config'
from keymapper import X
X.find_devices = lambda: ({
from keymapper import presets
presets.get_devices = lambda: ({
'device 1': ['/dev/input/event10', '/dev/input/event11'],
'device 2': ['/dev/input/event3']
})

@ -19,30 +19,11 @@
# along with key-mapper. If not, see <https://www.gnu.org/licenses/>.
import os
import unittest
from keymapper.logger import update_verbosity
from keymapper.X import parse_libinput_list, parse_evtest
class TestX(unittest.TestCase):
def check_result(self, result):
count = 0
for name, paths in result.items():
self.assertIsInstance(name, str)
self.assertIsInstance(paths, list)
for path in paths:
self.assertIsInstance(path, str)
self.assertTrue(path.startswith('/dev/input/event'))
count += 1
self.assertGreater(count, 0)
def test_libinput(self):
self.check_result(parse_libinput_list())
def test_evtest(self):
self.check_result(parse_evtest())
pass
if __name__ == "__main__":

@ -24,7 +24,7 @@ import unittest
import shutil
import time
from keymapper.presets import find_newest_preset, create_preset
from keymapper.presets import find_newest_preset, create_preset, get_devices
tmp = '/tmp/key-mapper-test'

Loading…
Cancel
Save