aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2022-03-19 14:05:24 +0100
committerAnton Khirnov <anton@khirnov.net>2022-03-19 14:05:24 +0100
commitd3d31eadcb4c210aea86698438af4371b9c4febc (patch)
tree6be4b8a2a92e54943cc43816090c0cdaf4886a72
parent75c36291b07d5a5bb5ceaec1979ac3e3f502ada3 (diff)
dev_add: add devices by name regex rather than path
Also, watch for new devices using udevadm monitor.
-rw-r--r--README5
-rwxr-xr-xdev_add109
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()