问题描述
我已经有一些可用的 Python 代码来检测某些 USB 设备类型的插入(来自此处).>
I already have some working Python code to detect the insertion of some USB device types (from here).
import wmi
raw_wql = "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA \'Win32_USBHub\'"
c = wmi.WMI()
watcher = c.watch_for(raw_wql=raw_wql)
while 1:
usb = watcher()
print(usb)
不幸的是,这个脚本没有检测到所有类型的 USB 设备的插入.这意味着检测到 USB 闪存驱动器的插入,但未检测到 USB 输入设备.根本没有检测到 USB 设备的移除.
Unfortunately this script does not detect the insertion of all types of USB devices. This means that the insertion of USB flash drives is detected, but USB input devices are not. The removal of USB devices is not detected at all.
有没有办法相应地扩展现有脚本?
Is there a way to extend the existing script accordingly?
更好的 WQL 查询和 Python 代码
我根据在 MSDN.
以下脚本旨在在插入或拔出 USB 键盘时输出消息.
The following script is intended to output a message when a USB keyboard is plugged in or unplugged.
问题:插入 USB 键盘时不会出现任何消息,但拔下 USB 键盘时会出现两条消息(键盘已连接"和键盘已断开").这段代码有什么问题?
Issue: No message appears when a USB keyboard is plugged in, but both messages ("Keyboard connected" and "Keyboard disconnected") appear when the USB keyboard is unplugged. What's wrong with this code?
import wmi
device_connected_wql = "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA \'Win32_Keyboard\'"
device_disconnected_wql = "SELECT * FROM __InstanceDeletionEvent WITHIN 2 WHERE TargetInstance ISA \'Win32_Keyboard\'"
c = wmi.WMI()
connected_watcher = c.watch_for(raw_wql=device_connected_wql)
disconnected_watcher = c.watch_for(raw_wql=device_disconnected_wql)
while 1:
connected = connected_watcher()
disconnected = disconnected_watcher()
if connected:
print("Keyboard connected")
if disconnected:
print("Keyboard disconnected")
推荐答案
有多种方法可以检测设备更改发生的情况
There are multiple ways to detect what is happening with device changes
使用 C++ 检测 Windows 中的 USB 插入/移除事件
一种建议的方法是使用 WM_DEVICECHANGE 消息.https://stackoverflow.com/a/4078996/2830850
One suggested way is to use WM_DEVICECHANGE message. https://stackoverflow.com/a/4078996/2830850
一个这样的例子可以在下面找到
One such example an be found at below
#Modified from: http://wiki.wxpython.org/HookingTheWndProc
##########################################################################
##
## This is a modification of the original WndProcHookMixin by Kevin Moore,
## modified to use ctypes only instead of pywin32, so it can be used
## with no additional dependencies in Python 2.5
##
##########################################################################
import sys
import ctypes
#import GUID
from ctypes import c_long, c_int, wintypes
import wx
GWL_WNDPROC = -4
WM_DESTROY = 2
DBT_DEVTYP_DEVICEINTERFACE = 0x00000005 # device interface class
DBT_DEVICEREMOVECOMPLETE = 0x8004 # device is gone
DBT_DEVICEARRIVAL = 0x8000 # system detected a new device
WM_DEVICECHANGE = 0x0219
class GUID(ctypes.Structure):
_pack_ = 1
_fields_ = [("Data1", ctypes.c_ulong),
("Data2", ctypes.c_ushort),
("Data3", ctypes.c_ushort),
("Data4", ctypes.c_ubyte * 8)]
## It's probably not neccesary to make this distinction, but it never hurts to be safe
if 'unicode' in wx.PlatformInfo:
SetWindowLong = ctypes.windll.user32.SetWindowLongW
CallWindowProc = ctypes.windll.user32.CallWindowProcW
else:
SetWindowLong = ctypes.windll.user32.SetWindowLongA
CallWindowProc = ctypes.windll.user32.CallWindowProcA
## Create a type that will be used to cast a python callable to a c callback function
## first arg is return type, the rest are the arguments
#WndProcType = ctypes.WINFUNCTYPE(c_int, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM)
WndProcType = ctypes.WINFUNCTYPE(ctypes.c_long, ctypes.c_int, ctypes.c_uint, ctypes.c_int, ctypes.c_int)
if 'unicode' in wx.PlatformInfo:
RegisterDeviceNotification = ctypes.windll.user32.RegisterDeviceNotificationW
else:
RegisterDeviceNotification = ctypes.windll.user32.RegisterDeviceNotificationA
RegisterDeviceNotification.restype = wintypes.HANDLE
RegisterDeviceNotification.argtypes = [wintypes.HANDLE, wintypes.c_void_p, wintypes.DWORD]
UnregisterDeviceNotification = ctypes.windll.user32.UnregisterDeviceNotification
UnregisterDeviceNotification.restype = wintypes.BOOL
UnregisterDeviceNotification.argtypes = [wintypes.HANDLE]
class DEV_BROADCAST_DEVICEINTERFACE(ctypes.Structure):
_fields_ = [("dbcc_size", ctypes.c_ulong),
("dbcc_devicetype", ctypes.c_ulong),
("dbcc_reserved", ctypes.c_ulong),
("dbcc_classguid", GUID),
("dbcc_name", ctypes.c_wchar * 256)]
class DEV_BROADCAST_HDR(ctypes.Structure):
_fields_ = [("dbch_size", wintypes.DWORD),
("dbch_devicetype", wintypes.DWORD),
("dbch_reserved", wintypes.DWORD)]
GUID_DEVCLASS_PORTS = GUID(0x4D36E978, 0xE325, 0x11CE,
(ctypes.c_ubyte*8)(0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18))
GUID_DEVINTERFACE_USB_DEVICE = GUID(0xA5DCBF10L, 0x6530,0x11D2,
(ctypes.c_ubyte*8)(0x90, 0x1F, 0x00,0xC0, 0x4F, 0xB9, 0x51, 0xED))
class WndProcHookMixin:
"""
This class can be mixed in with any wxWindows window class in order to hook it's WndProc function.
You supply a set of message handler functions with the function addMsgHandler. When the window receives that
message, the specified handler function is invoked. If the handler explicitly returns False then the standard
WindowProc will not be invoked with the message. You can really screw things up this way, so be careful.
This is not the correct way to deal with standard windows messages in wxPython (i.e. button click, paint, etc)
use the standard wxWindows method of binding events for that. This is really for capturing custom windows messages
or windows messages that are outside of the wxWindows world.
"""
def __init__(self):
self.__msgDict = {}
## We need to maintain a reference to the WndProcType wrapper
## because ctypes doesn't
self.__localWndProcWrapped = None
self.rtnHandles = []
def hookWndProc(self):
self.__localWndProcWrapped = WndProcType(self.localWndProc)
self.__oldWndProc = SetWindowLong(self.GetHandle(), GWL_WNDPROC, self.__localWndProcWrapped)
def unhookWndProc(self):
SetWindowLong(self.GetHandle(), GWL_WNDPROC, self.__oldWndProc)
## Allow the ctypes wrapper to be garbage collected
self.__localWndProcWrapped = None
def addMsgHandler(self,messageNumber,handler):
self.__msgDict[messageNumber] = handler
def localWndProc(self, hWnd, msg, wParam, lParam):
# call the handler if one exists
# performance note: "in" is the fastest way to check for a key
# when the key is unlikely to be found
# (which is the case here, since most messages will not have handlers).
# This is called via a ctypes shim for every single windows message
# so dispatch speed is important
if msg in self.__msgDict:
# if the handler returns false, we terminate the message here
# Note that we don't pass the hwnd or the message along
# Handlers should be really, really careful about returning false here
if self.__msgDict[msg](wParam,lParam) == False:
return
# Restore the old WndProc on Destroy.
if msg == WM_DESTROY: self.unhookWndProc()
return CallWindowProc(self.__oldWndProc, hWnd, msg, wParam, lParam)
def registerDeviceNotification(self, guid, devicetype=DBT_DEVTYP_DEVICEINTERFACE):
devIF = DEV_BROADCAST_DEVICEINTERFACE()
devIF.dbcc_size = ctypes.sizeof(DEV_BROADCAST_DEVICEINTERFACE)
devIF.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE
if guid:
devIF.dbcc_classguid = GUID.GUID(guid)
return RegisterDeviceNotification(self.GetHandle(), ctypes.byref(devIF), 0)
def unregisterDeviceNotification(self, handle):
if UnregisterDeviceNotification(handle) == 0:
raise Exception("Unable to unregister device notification messages")
# a simple example
if __name__ == "__main__":
class MyFrame(wx.Frame,WndProcHookMixin):
def __init__(self,parent):
WndProcHookMixin.__init__(self)
wx.Frame.__init__(self,parent,-1,"Insert and Remove USE Device and Watch STDOUT",size=(640,480))
self.Bind(wx.EVT_CLOSE, self.onClose)
#Change the following guid to the GUID of the device you want notifications for
#self.devNotifyHandle = self.registerDeviceNotification(guid="{3c5e1462-5695-4e18-876b-f3f3d08aaf18}")
dbh = DEV_BROADCAST_DEVICEINTERFACE()
dbh.dbcc_size = ctypes.sizeof(DEV_BROADCAST_DEVICEINTERFACE)
dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE
dbh.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE
self.devNotifyHandle = RegisterDeviceNotification(self.GetHandle(), ctypes.byref(dbh), 0)
self.addMsgHandler(WM_DEVICECHANGE, self.onDeviceChange)
self.hookWndProc()
def onDeviceChange(self,wParam,lParam):
print "WM_DEVICECHANGE [WPARAM:%i][LPARAM:%i]"%(wParam,lParam)
if wParam == DBT_DEVICEARRIVAL:
print "Device Arrival"
elif wParam == DBT_DEVICEREMOVECOMPLETE:
print "Device Remvoed"
if lParam:
dbh = DEV_BROADCAST_HDR.from_address(lParam)
if dbh.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE:
dbd = DEV_BROADCAST_DEVICEINTERFACE.from_address(lParam)
#Verify that the USB VID and PID match our assigned VID and PID
if 'Vid_10c4&Pid_8382' in dbd.dbcc_name:
print "Was Our USB Device"
return True
def onClose(self, event):
self.unregisterDeviceNotification(self.devNotifyHandle)
event.Skip()
app = wx.App(False)
frame = MyFrame(None)
frame.Show()
app.MainLoop()
以上摘自https://github.com/4wei"/blob/fa8603b92cb0b3f6ce00c876f24138211f47e906/HookUsbMsg.py
现在回到你的代码
import wmi
device_connected_wql = "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA \'Win32_Keyboard\'"
device_disconnected_wql = "SELECT * FROM __InstanceDeletionEvent WITHIN 2 WHERE TargetInstance ISA \'Win32_Keyboard\'"
c = wmi.WMI()
connected_watcher = c.watch_for(raw_wql=device_connected_wql)
disconnected_watcher = c.watch_for(raw_wql=device_disconnected_wql)
while 1:
connected = connected_watcher()
disconnected = disconnected_watcher()
if connected:
print("Keyboard connected")
if disconnected:
print("Keyboard disconnected")
你调用的方式是connected_watcher
和disconnected_watcher
串联,都是阻塞调用.因此,当您首先调用它时 connected
变为真,然后 disconnected_watcher
被调用,直到设备断开连接为止.这就是为什么当您断开连接时,您会同时看到两条消息.
The way you call is connected_watcher
and disconnected_watcher
in series, both are blocking call. So when you call it first connected
becomes true and then the disconnected_watcher
gets called which blocked until a device is disconnected. That is why when you disconnected, you see both the messages together.
修复它的方法是确保超时这些查询
The way you can fix it is by making sure you time out these queries
while 1:
try:
connected = connected_watcher(timeout_ms=10)
except wmi.x_wmi_timed_out:
pass
else:
if connected:
print("Keyboard connected")
try:
disconnected = disconnected_watcher(timeout_ms=10)
except wmi.x_wmi_timed_out:
pass
else:
if disconnected:
print("Keyboard disconnected")
另一种方法是使用线程.但是为了让你的代码线程兼容,你需要像下面这样
Another approach is to use threads. But to make your code thread compatible you need to make it like below
class VolumeRemovalWatcher:
def __init__(self, callback=None):
self.stop_wanted=False
self.callback=callback
def stop(self):
self.stop_wanted = True
def watch_for_events(self):
if not threading.current_thread() is threading.main_thread():
pythoncom.CoInitialize()
try:
w = WMI()
watcher = w.Win32_VolumeChangeEvent.watch_for(EventType=3)
while not self.stop_wanted:
try:
event = watcher(timeout_ms=1000)
except x_wmi_timed_out:
pass
else:
print(event.DriveName)
if self.callback is not None:
self.callback(event.DriveName)
except Exception as e:
print(e)
return None
finally:
if not threading.current_thread() is threading.main_thread():
pythoncom.CoUninitialize()
这篇关于在 Windows 10 上检测 USB 输入设备的插入/移除的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!