检测热插拔事件的Python脚本

2 投票
1 回答
2367 浏览
提问于 2025-04-18 00:06

我正在尝试用Python来检测鼠标和键盘的事件,并且希望在检测过程中能够容忍热插拔的操作。我写了一个脚本,可以在运行时自动检测键盘和鼠标的插入,并输出所有的键盘和鼠标事件。我使用了evdev和pyudev这两个库来实现这个功能。我的脚本大部分都能正常工作,包括键盘和鼠标事件的检测以及插入的检测。然而,每当我拔掉鼠标时,会发生很多奇怪的事情,导致我的脚本无法正常运行。我在这里有几个困惑。

(1) 每当鼠标插入系统时,/dev/input/文件夹下会生成两个文件,分别是./mouseX和./eventX。我尝试用cat命令查看这两个文件的输出,确实有区别,但我不明白为什么即使./eventX已经存在,Linux还会生成./mouseX?

(2) 每当我拔掉鼠标时,./mouseX的拔出事件会先发生,而我在evdev中并没有使用这个事件,这导致我的脚本失败,因为./eventX(我在脚本中读取数据的地方)同时也被拔掉了,但我只能在下一轮检测到./eventX。我在脚本中使用了一个小技巧(变量i)来绕过这个问题,但即使我成功删除了鼠标设备,select.select()仍然开始无限读取输入,尽管我没有在键盘上输入任何东西。

下面是我的脚本(根据之前的帖子修改而来),感谢大家的关注!

#!/usr/bin/env python

import pyudev
from evdev import InputDevice, list_devices, categorize
from select import select

context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='input')
monitor.start()

devices = map(InputDevice, list_devices())
dev_paths = []
finalizers = []

for dev in devices:
    if "keyboard" in dev.name.lower():
        dev_paths.append(dev.fn)
    elif "mouse" in dev.name.lower():
        dev_paths.append(dev.fn)

devices = map(InputDevice, dev_paths)
devices = {dev.fd : dev for dev in devices}
devices[monitor.fileno()] = monitor
count = 1

while True:
    r, w, x = select(devices, [], [])

    if monitor.fileno() in r:
        r.remove(monitor.fileno())

        for udev in iter(functools.partial(monitor.poll, 0), None):
            # we're only interested in devices that have a device node
            # (e.g. /dev/input/eventX)
            if not udev.device_node:
                break

            # find the device we're interested in and add it to fds
            for name in (i['NAME'] for i in udev.ancestors if 'NAME' in i):
                # I used a virtual input device for this test - you
                # should adapt this to your needs
                if 'mouse' in name.lower() and 'event' in udev.device_node:
                    if udev.action == 'add':
                        print('Device added: %s' % udev)
                        dev = InputDevice(udev.device_node)
                        devices[dev.fd] = dev
                        break
                    if udev.action == 'remove':
                        print('Device removed: %s' % udev)
                        finalizers.append(udev.device_node)
                        break


    for path in finalizers:
        for dev in devices.keys():
            if dev != monitor.fileno() and devices[dev].fn == path:
                print "delete the device from list"
                del devices[dev]

    for i in r:
        if i in devices.keys() and count != 0:
            count = -1
            for event in devices[i].read():
                count = count + 1
                print(categorize(event))

1 个回答

2

简单来说,mouseX和eventX的区别在于,eventX是来自evdev设备的,而mouseX是“传统”的设备(比如,它不支持各种evdev的ioctl命令)。

我不太清楚你发的代码有什么问题,不过这里有一段正确的代码示例。

#!/usr/bin/env python

import pyudev
import evdev
import select
import sys
import functools
import errno

context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='input')
# NB: Start monitoring BEFORE we query evdev initially, so that if
# there is a plugin after we evdev.list_devices() we'll pick it up
monitor.start()

# Modify this predicate function for whatever you want to match against
def pred(d):
    return "keyboard" in d.name.lower() or "mouse" in d.name.lower()

# Populate the "active devices" map, mapping from /dev/input/eventXX to
# InputDevice
devices = {}
for d in map(evdev.InputDevice, evdev.list_devices()):
    if pred(d):
        print d
        devices[d.fn] = d

# "Special" monitor device
devices['monitor'] = monitor

while True:
    rs, _, _ = select.select(devices.values(), [], [])
    # Unconditionally ping monitor; if this is spurious this
    # will no-op because we pass a zero timeout.  Note that
    # it takes some time for udev events to get to us.
    for udev in iter(functools.partial(monitor.poll, 0), None):
        if not udev.device_node: break
        if udev.action == 'add':
            if udev.device_node not in devices:
                print "Device added: %s" % udev
                try:
                    devices[udev.device_node] = evdev.InputDevice(udev.device_node)
                except IOError, e:
                    # udev reports MORE devices than are accessible from
                    # evdev; a simple way to check is see if the devinfo
                    # ioctl fails
                    if e.errno != errno.ENOTTY: raise
                    pass
        elif udev.action == 'remove':
            # NB: This code path isn't exercised very frequently,
            # because select() will trigger a read immediately when file
            # descriptor goes away, whereas the udev event takes some
            # time to propagate to us.
            if udev.device_node in devices:
                print "Device removed (udev): %s" % devices[udev.device_node]
                del devices[udev.device_node]
    for r in rs:
        # You can't read from a monitor
        if r.fileno() == monitor.fileno(): continue
        if r.fn not in devices: continue
        # Select will immediately return an fd for read if it will
        # ENODEV.  So be sure to handle that.
        try:
            for event in r.read():
                pass
                print evdev.categorize(event)
        except IOError, e:
            if e.errno != errno.ENODEV: raise
            print "Device removed: %s" % r
            del devices[r.fn]

撰写回答