|
|
|
@ -19,16 +19,23 @@
|
|
|
|
|
# along with key-mapper. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""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.
|
|
|
|
|
def find_devices():
|
|
|
|
|
"""Return a mapping of {name: [ids]} for each input device.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
type : string
|
|
|
|
|
Ine of 'id' or 'name'
|
|
|
|
|
Evtest listing is really slow, query this only once when the
|
|
|
|
|
program starts.
|
|
|
|
|
"""
|
|
|
|
|
output = subprocess.check_output(['xinput', 'list', f'--{type}-only'])
|
|
|
|
|
return [line for line in output.decode().split('\n') if line != '']
|
|
|
|
|
# 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')
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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')
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
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 != '']
|
|
|
|
|