#!/usr/bin/python3 import argparse import contextlib import glob import logging import os import re import selectors import subprocess import sys import time def dev_match(evpath, pattern, logger): name = '(null)' phys = '(null)' try: with open(evpath + '/device/name', 'r') as f: name = f.read().strip() with open(evpath + '/device/phys', 'r') as f: phys = f.read().strip() 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 (%s | %s) did not match the pattern', evpath, name, phys) 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'), args.control) if not os.path.exists(control_path): 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()) # 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') sys.exit(1) # 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)