From d3d31eadcb4c210aea86698438af4371b9c4febc Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Sat, 19 Mar 2022 14:05:24 +0100 Subject: dev_add: add devices by name regex rather than path Also, watch for new devices using udevadm monitor. --- README | 5 +-- dev_add | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 93 insertions(+), 21 deletions(-) diff --git a/README b/README index 8f6361f..e7fedf4 100644 --- a/README +++ b/README @@ -57,5 +57,6 @@ The commands are: dev_add ------- A helper tool that accepts the name (or full path) of the control FIFO and a -real device node. It adds that device through the FIFO, then sleeps until -interrupted. On exit it removes the device it added. +regex pattern. It adds all input devices whose name matches the pattern +(monitoring newly newly added devices using udevadm). On exit it clears all +bound devices (unless the --no-clear option is used). diff --git a/dev_add b/dev_add index ba7ab6b..1e0b092 100755 --- a/dev_add +++ b/dev_add @@ -1,37 +1,108 @@ #!/usr/bin/python3 +import argparse import contextlib +import glob +import logging import os +import re import selectors +import subprocess import sys import time -control_path = os.path.expanduser(sys.argv[1]) +def dev_match(evpath, pattern, logger): + try: + with open(evpath + '/device/name', 'r') as f: + name = f.read() + with open(evpath + '/device/phys', 'r') as f: + phys = f.read() + + if not 'uinput' in phys and re.search(pattern, name): + logger.debug('%s matched the pattern', evpath) + return True + except IOError: + pass + + logger.debug('%s did not match the pattern', evpath) + return False + +def dev_add(control, syspath, logger): + devpath = '/dev/input/' + os.path.basename(syspath) + control.write('add ' + devpath + '\n') + control.flush() + + logger.info('Added device: %s', devpath) + +parser = argparse.ArgumentParser() + +parser.add_argument('-v', '--verbose', action = 'count', default = 0) +parser.add_argument('-q', '--quiet', action = 'count', default = 0) +parser.add_argument('-n', '--no-clear', action = 'store_true') +parser.add_argument('control') +parser.add_argument('device') + +args = parser.parse_args(sys.argv[1:]) + +# setup logging +# default to 20 (INFO), every -q goes a level up, every -v a level down +log_level = max(10 * (2 + args.quiet - args.verbose), 0) +log_format = '%(asctime)s:%(name)s:%(levelname)s: %(message)s' +logging.basicConfig(format = log_format, datefmt = '%F %T', level = log_level) +logger = logging.getLogger(os.path.basename(sys.argv[0])) + +control_path = os.path.expanduser(args.control) if not control_path or control_path[0] != '/': control_path = os.path.join(os.path.expanduser('~/.cache/inputfwd'), - sys.argv[1]) + args.control) if not os.path.exists(control_path): - sys.stderr.write('Control path "%s"->"%s" does not exist.\n' % (sys.argv[1], control_path)) - -devpath = sys.argv[2] -if not os.path.exists(devpath): - sys.stderr.write('Device path "%s" does not exist.\n' % devpath) + sys.stderr.write('Control path "%s"->"%s" does not exist.\n' % (args.control, control_path)) with contextlib.ExitStack() as stack: sel = stack.enter_context(selectors.DefaultSelector()) - f = stack.enter_context(open(control_path, 'w')) + # open the control FIFO + control = stack.enter_context(open(control_path, 'w')) + + # do the initial scan + for dev in glob.glob('/sys/class/input/event*'): + if dev_match(dev, args.device, logger): + dev_add(control, dev, logger) + + # monitor new input devices + udevadm = subprocess.Popen(['udevadm', 'monitor', '--subsystem-match=input', '--udev'], + stdout = subprocess.PIPE, bufsize = 0) + stack.callback(lambda p: p.terminate(), udevadm) + + sel.register(control, selectors.EVENT_READ) + sel.register(udevadm.stdout, selectors.EVENT_READ) + + while True: + try: + events = sel.select() + except KeyboardInterrupt: + logger.info('Interrupted') + if not args.no_clear: + logger.info('Clearing devices') + control.write('clear\n') + control.flush() + break + + for key, mask in events: + # read event on the control pipe means remote hangup + if key.fileobj == control: + logger.error('Remote end hung up, terminating') + break + + # udevadm event + line = key.fileobj.read(4096).decode('ascii').strip() + logger.debug('udevadm line: %s', line) + + m = re.search(r'add\s+(\S+/event\d+)', line) + if m is not None: + syspath = '/sys' + m.group(1) + if dev_match(syspath, args.device, logger): + dev_add(control, syspath, logger) - f.write('add ' + devpath + '\n') - f.flush() - # sleep until interrupted - sel.register(f, selectors.EVENT_READ) - try: - sel.select() - sys.stderr.write('Remote end hung up.\n') - except KeyboardInterrupt: - sys.stderr.write('Interrupted, removing device and existing\n') - f.write('remove ' + devpath + '\n') - f.flush() -- cgit v1.2.3