Reputation: 155
I'm trying to use python to detect mouse and keyboard event, and tolerant the hot-plug action during the detection. I write this script to automatically detect the keyboard and mouse plug-ins in run-time and output all the keyboard and mouse events. I use evdev and pyudev packages to realize this function. I have my scripts mostly working, including keyboard and mouse event detection and plug-in detection. However, whenever I plug-out the mouse, many weird things happen and my script could not work properly. I have several confusions here.
(1) Whenever the mouse is plugged into the system, there are two files generated in /dev/input/ folder, including ./mouseX and ./eventX. I try to cat to see the output from both source and there are indeed differences, but I do not understand why linux will have ./mouseX even if ./eventX already exists?
(2) Whenever I unplug my mouse, the ./mouseX unplug event comes first, which I did not use in evdev, and this leads to the failure of the script because ./eventX(where I read the data in the script) is unplugged simultaneously but I could only detect ./eventX in the next round. I use a trick(variable i in my script) to bypass this issue, but even though I could successfully delete the mouse device, the select.select() begins endless input reading even though I did not type anything to the keyboard.
The script is listed below(modified based on answers from previous post), thanks beforehand for your attention!
#!/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))
Upvotes: 2
Views: 2470
Reputation: 26762
The difference between mouseX and eventX is, generally speaking, is eventX is the evdev device, whereas mouseX is the "traditional" device (which, for example, doesn't support various evdev ioctls.)
I don't know what's wrong with the code you posted, but here is a code snippet which does the right thing.
#!/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]
Upvotes: 2