1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
#!/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):
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'),
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')
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)
|