aboutsummaryrefslogtreecommitdiff
path: root/dev_add
blob: 5e534512f8c105a2d23068a67df2731fd0e5d223 (plain)
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
109
110
111
#!/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)