在 Python 中作彤,檢測(cè)熱插拔事件(例如 USB 設(shè)備插入或移除)可以通過(guò)以下幾種方法實(shí)現(xiàn)覆山。最常用的方式是使用 pyudev 庫(kù)(適用于 Linux 系統(tǒng))來(lái)監(jiān)聽系統(tǒng)設(shè)備的變化俐东。
1脖镀、問(wèn)題背景
用戶正在嘗試使用 Python 來(lái)檢測(cè)鼠標(biāo)和鍵盤事件公给,并在檢測(cè)過(guò)程中容忍熱插拔操作基协。用戶編寫的腳本可以自動(dòng)檢測(cè)運(yùn)行時(shí)環(huán)境中的鍵盤和鼠標(biāo)插件,并輸出所有鍵盤和鼠標(biāo)事件剑按。用戶使用了 evdev 和 pyudev 包來(lái)實(shí)現(xiàn)此功能疾就。腳本大部分都可以正常工作,包括鍵盤和鼠標(biāo)事件檢測(cè)以及插件檢測(cè)艺蝴。然而猬腰,每當(dāng)用戶拔出鼠標(biāo)時(shí),都會(huì)發(fā)生許多奇怪的事情猜敢,導(dǎo)致腳本無(wú)法正常工作姑荷。
(1)每當(dāng)將鼠標(biāo)插入系統(tǒng)時(shí),/dev/input/ 文件夾中都會(huì)生成兩個(gè)文件缩擂,包括 ./mouseX 和 ./eventX鼠冕。用戶嘗試通過(guò) cat 命令查看兩個(gè)源的輸出,發(fā)現(xiàn)確實(shí)有差異撇叁,但我不理解為什么 Linux 會(huì)在 ./eventX 已經(jīng)存在的情況下仍然會(huì)有 ./mouseX。
(2)每當(dāng)用戶拔下鼠標(biāo)時(shí)畦贸,./mouseX 拔下事件會(huì)首先發(fā)生陨闹,但我在 evdev 中沒(méi)有使用它,這會(huì)導(dǎo)致腳本失敗薄坏,因?yàn)?./eventX(腳本中讀取數(shù)據(jù)的位置)也會(huì)同時(shí)拔下趋厉,但我只能在下一輪中檢測(cè)到 ./eventX。我使用了一個(gè)技巧(腳本中的變量 i)來(lái)繞過(guò)這個(gè)問(wèn)題胶坠,但即使我能夠成功刪除鼠標(biāo)設(shè)備君账,select.select() 也會(huì)開始無(wú)限期地讀取輸入,即使我沒(méi)有對(duì)鍵盤輸入任何內(nèi)容沈善。
以下是腳本的內(nèi)容(根據(jù)先前帖子的答案進(jìn)行了修改)乡数,非常感謝您事先的關(guān)注椭蹄!
#!/usr/bin/env python
importpyudev
fromevdevimportInputDevice,list_devices,categorize
fromselectimportselect
context=pyudev.Context()
monitor=pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='input')
monitor.start()
devices=map(InputDevice,list_devices())
dev_paths=[]
finalizers=[]
fordevindevices:
if"keyboard"indev.name.lower():
dev_paths.append(dev.fn)
elif"mouse"indev.name.lower():
dev_paths.append(dev.fn)
devices=map(InputDevice,dev_paths)
devices={dev.fd:devfordevindevices}
devices[monitor.fileno()]=monitor
count=1
whileTrue:
r,w,x=select(devices, [], [])
ifmonitor.fileno()inr:
r.remove(monitor.fileno())
forudeviniter(functools.partial(monitor.poll,0),None):
# we're only interested in devices that have a device node
# (e.g. /dev/input/eventX)
ifnotudev.device_node:
break
# find the device we're interested in and add it to fds
fornamein(i['NAME']foriinudev.ancestorsif'NAME'ini):
# I used a virtual input device for this test - you
# should adapt this to your needs
if'mouse'inname.lower()and'event'inudev.device_node:
ifudev.action=='add':
print('Device added: %s'%udev)
dev=InputDevice(udev.device_node)
devices[dev.fd]=dev
break
ifudev.action=='remove':
print('Device removed: %s'%udev)
finalizers.append(udev.device_node)
break
forpathinfinalizers:
fordevindevices.keys():
ifdev!=monitor.fileno()anddevices[dev].fn==path:
print"delete the device from list"
deldevices[dev]
foriinr:
ifiindevices.keys()andcount!=0:
count=-1
foreventindevices[i].read():
count=count+1
print(categorize(event))
2、解決方案
答案1:
mouseX 和 eventX 之間的區(qū)別在于净赴,一般來(lái)說(shuō)绳矩,eventX 是 evdev 設(shè)備,而 mouseX 是“傳統(tǒng)”設(shè)備(例如玖翅,不支持各種 evdev ioctl)翼馆。
我不知道你發(fā)布的代碼出了什么問(wèn)題,但這里有一個(gè)代碼片段可以解決這個(gè)問(wèn)題金度。
#!/usr/bin/env python
importpyudev
importevdev
importselect
importsys
importfunctools
importerrno
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
defpred(d):
return"keyboard"ind.name.lower()or"mouse"ind.name.lower()
# Populate the "active devices" map, mapping from /dev/input/eventXX to
# InputDevice
devices={}
fordinmap(evdev.InputDevice,evdev.list_devices()):
ifpred(d):
printd
devices[d.fn]=d
# "Special" monitor device
devices['monitor']=monitor
whileTrue:
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.
forudeviniter(functools.partial(monitor.poll,0),None):
ifnotudev.device_node:break
ifudev.action=='add':
ifudev.device_nodenotindevices:
print"Device added: %s"%udev
try:
devices[udev.device_node]=evdev.InputDevice(udev.device_node)
exceptIOError,e:
# udev reports MORE devices than are accessible from
# evdev; a simple way to check is see if the devinfo
# ioctl fails
ife.errno!=errno.ENOTTY:raise
pass
elifudev.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.
ifudev.device_nodeindevices:
print"Device removed (udev): %s"%devices[udev.device_node]
deldevices[udev.device_node]
forrinrs:
# You can't read from a monitor
ifr.fileno()==monitor.fileno():continue
ifr.fnnotindevices:continue
# Select will immediately return an fd for read if it will
# ENODEV.? So be sure to handle that.
try:
foreventinr.read():
pass
printevdev.categorize(event)
exceptIOError,e:
ife.errno!=errno.ENODEV:raise
print"Device removed: %s"%r
deldevices[r.fn]
然后你可以用類似的方式來(lái)監(jiān)聽事件应媚。