diff --git a/README.md b/README.md index 98aa4488..1d29f053 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ work.

+# Dependencies + +`evtest` + # Roadmap - [x] show a dropdown to select an arbitrary device from `xinput list` diff --git a/bin/key-mapper-gtk b/bin/key-mapper-gtk index 3b241292..7272705a 100755 --- a/bin/key-mapper-gtk +++ b/bin/key-mapper-gtk @@ -114,7 +114,8 @@ class Window: """Make the devices selectable.""" devices = find_devices() device_selection = self.get('device_selection') - for (id, device) in devices: + for device in devices: + ids = devices[device] device_selection.append(device, device) def populate_presets(self): @@ -132,7 +133,6 @@ class Window: if isinstance(device, Gtk.ComboBoxText): device = device.get_active_text() - device = device.get_active_text() presets = get_presets(device) if len(presets) == 0: create_preset(device) diff --git a/keymapper/X.py b/keymapper/X.py index afbf71e2..2d142692 100644 --- a/keymapper/X.py +++ b/keymapper/X.py @@ -19,16 +19,23 @@ # along with key-mapper. If not, see . -"""Stuff that interacts with the X Server""" +"""Stuff that interacts with the X Server + +Resources: +https://wiki.archlinux.org/index.php/Keyboard_input +http://people.uleth.ca/~daniel.odonnell/Blog/custom-keyboard-in-linuxx11 +""" import re import subprocess +from keymapper.logger import logger -# mapping of keycode to character, e.g. 38 to the 'A' key of the keyboard. + +# mapping of key to character # This depends on the configured keyboard layout. -# example: 38: "a A a A ae AE ae" +# example: AC01: "a A a A ae AE ae". key_mapping = {} @@ -42,30 +49,67 @@ def load_keymapping(): key_mapping[search[0]] = search[1] -def get_xinput_list(type): - """Run xinput and get the result as list. - - Parameters - ---------- - type : string - Ine of 'id' or 'name' - """ - output = subprocess.check_output(['xinput', 'list', f'--{type}-only']) - return [line for line in output.decode().split('\n') if line != ''] - - def find_devices(): - """Get a list of (id, name) for each input device.""" - # `xinput list` - ids = get_xinput_list('id') - names = get_xinput_list('name') + """Return a mapping of {name: [ids]} for each input device. - # names contains duplicates and "Virtual"-somethings, filter those - known_names = [] - # TODO remember all IDS? try each one of them for setxkbmap until success? - result = [] - for (id, name) in zip(ids, names): - if name not in known_names and not name.startswith('Virtual'): - known_names.append(name) - result.append((id, name)) - return result + Evtest listing is really slow, query this only once when the + program starts. + """ + # 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)) + + devices = {} + # there may be multiple entries per device in /dev, because one handles + # movement while the other handles extra buttons. Remember all of the + # device ids, so that the input mapping can be applied to all matching + # ids, one of them is going to be the right one. + for line in evtest: + match = re.search(r'event(\d+):\s+(.+)', line) + if match is None: + continue + + # the id refers to a file in /dev/input, it is different from + # the id that `xinput list` can return. + id = match[1] + name = match[2] + + if name not in xinput: + continue + + # there can be + # 'Logitech USB Keyboard' and + # 'Logitech USB Keyboard Consumer Control' + if not devices.get(name): + devices[name] = [] + devices[name].append(id) + + logger.info('Devices: %s', ', '.join(list(devices.keys()))) + + return devices + + +def get_xinput_list(): + """Run xinput and get the result as list.""" + xinput = subprocess.check_output(['xinput', 'list', f'--name-only']) + return [line for line in xinput.decode().split('\n') if line != '']