Pythen 监听 USB 存储器插入、拔出操作
Windows 设备管理事件
WM_DEVICECHANGE 标识符
事件 | 值 | 说明 |
---|---|---|
DBT_DEVNODES_CHANGED | 0x0007 | 已向系统添加或删除设备。 |
DBT_DEVICEARRIVAL | 0x8000 | 当插入设备或媒体块并变为可用时,系统会广播DBT_DEVICEARRIVAL设备事件。 |
DBT_DEVICEQUERYREMOVE | 0x8001 | 系统广播DBT_DEVICEQUERYREMOVE设备事件以请求删除设备或媒体块的权限。 此消息是应用程序和驱动程序准备进行此删除的最后一次机会。 但是,任何应用程序都可以拒绝此请求并取消操作。 |
DBT_DEVICEQUERYREMOVEFAILED | 0x8002 | 系统在取消删除设备或媒体块的请求时广播DBT_DEVICEQUERYREMOVEFAILED设备事件。 |
DBT_DEVICEMOVEPENDING | 0x8003 | 系统在删除设备或媒体块且不再可供使用时广播DBT_DEVICEREMOVEPENDING设备事件。 |
DBT_DEVICEREMOVECOMPLETE | 0x8004 | 当设备或媒体被物理删除时,系统会广播DBT_DEVICEREMOVECOMPLETE设备事件。 |
DBT_DEVICETYPESSPECIFIC | 0x8005 | 系统在发生特定于设备的事件时,广播DBT_DEVICETYPESPECIFIC设备事件。 |
DBT_CONFIGCHANGED | 0x0018 | 系统广播DBT_CONFIGCHANGED设备事件,以指示由于停靠或取消停靠而更改了当前配置。 在HKEY_CURRENT_CONFIG键下的注册表中存储数据的应用程序或驱动程序应更新数据。 |
实例
USB 非存储设备
设备类型 | 是否首次插入 | 插入注册触发信号次数 | 拔出销毁触发信号次数 |
---|---|---|---|
USB 有线键盘 | 是 | 4 | 2 |
USB 有线键盘 | 否 | 3 | 2 |
USB 2.4G 无线接收器(键盘、鼠标) | 是 | 5 | 2 |
USB 2.4G 无线接收器(键盘、鼠标) | 否 | 3 | 2 |
USB 有线键盘
首次插入流程:
# 第一个或为电平变化。USB 设备拔出,线路短路引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为控制传输。由主控器发送给目标设备,目标设备返回地址、端口号、设备描述符、设备类型等,目标设备注册,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第三个或为设备启动。由主控器识别为 USB 输入设备,触发 DBT_DEVICEARRIVAL 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第四个或为更新索引。目标设备索引读取完成,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
非首次插入流程:
# 第一个或为电平变化。USB 设备拔出,线路短路引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为设备启动。无需注册,由主控器识别为 USB 输入设备,触发 DBT_DEVICEARRIVAL 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第三个个或为更新索引。目标设备索引读取完成,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
拔出流程:
# 第一个或为电平变化。USB 设备拔出,线路短路引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为设备列表更新。触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
USB 2.4G 无线接收器(键盘、鼠标)
首次插入流程:
# 第一个或为电平变化。USB 设备拔出,线路短路引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为控制传输。由主控器发送给目标设备,目标设备返回地址、端口号、设备描述符、设备类型等,目标设备注册,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第三个或为驱动安装。由主控器发送给目标设备信息,目标设备安装驱动,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第四个或为设备启动。由主控器识别为 USB 输入设备,触发 DBT_DEVICEARRIVAL 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第五个或为更新索引。目标设备索引读取完成,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
非首次插入流程:
# 第一个或为电平变化。USB 设备拔出,线路短路引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为设备可用。无需注册,鼠标驱动安装在主机中,主机与目标设备通信通过厂商的驱动进行,由驱动识别设备可用性,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第三个个或为更新索引。目标设备索引读取完成,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
拔出流程:
# 第一个或为电平变化。USB 设备拔出,线路短路引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为设备列表更新。触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
USB 存储设备
设备类型 | 是否首次插入 | 插入注册触发信号次数 | 拔出销毁触发信号次数 |
---|---|---|---|
USB 2.0 U 盘 | 是/否 | 5 | 3 |
USB 3.0 U 盘 | 是/否 | 4 | 3 |
下述流程不一定对,有错误请指出或辩证看待。
USB 2.0 U 盘
插入流程:
# 第一个或为控制传输。USB 设备插入,线路短接引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为控制传输。由主控器发送给目标设备,目标设备返回地址、端口号、设备描述符、设备类型等,目标设备注册,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第三个或为磁盘可用。由主控器划分盘符,主机识别为 USB 存储设备,触发 DBT_DEVICEARRIVAL 广播。
wparam:|DBT_DEVICEARRIVAL:0x8000|
lparam:|0xa27b5ef200|
drive_letter:|5|
ch( ord('A') + drive_letter):|F|
# 第四个或为设备列表更新。触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第五个或为更新磁盘索引。受限于协议或速度,目标设备索引读取完成,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
拔出流程:
# 第一个或为电平变化。USB 设备拔出,线路短路引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为设备销毁。由主控器发送给目标设备,目标设备未响应,由主控器移除盘符,确认已删除设备或媒体片段,触发 DBT_DEVICEREMOVECOMPLETE 广播。
wparam:|DBT_DEVICEREMOVECOMPLETE:0x8004|
lparam:|0xa27b5ef200|
drive_letter:|5|
ch( ord('A') + drive_letter):|F|
# 第三个或为设备列表更新。触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
USB 3.0 U 盘
插入流程:
# 第一个或为控制传输。USB 设备插入,线路短接引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为控制传输。由主控器发送给目标设备,目标设备返回地址、端口号、设备描述符、设备类型等,目标设备注册,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第三个或为磁盘可用。由主控器划分盘符,主机识别为 USB 存储设备,触发 DBT_DEVICEARRIVAL 广播。
wparam:|DBT_DEVICEARRIVAL:0x8000|
lparam:|0xa27b5ef200|
drive_letter:|5|
ch( ord('A') + drive_letter):|F|
# 第四个或为设备列表更新。触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
拔出流程:
# 第一个或为电平变化。USB 设备拔出,线路短路引发电平变化,有主控器识别,判断设备变化,触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
# 第二个或为设备销毁。由主控器发送给目标设备,目标设备未响应,由主控器移除盘符,确认已删除设备或媒体片段,触发 DBT_DEVICEREMOVECOMPLETE 广播。
wparam:|DBT_DEVICEREMOVECOMPLETE:0x8004|
lparam:|0xa27b5ef200|
drive_letter:|5|
ch( ord('A') + drive_letter):|F|
# 第三个或为设备列表更新。触发 DBT_DEVNODES_CHANGED 广播。
wparam:|DBT_DEVNODES_CHANGED:0x7|
lparam:|0x0|
参考
Python 代码
所指皆为 Windows 平台。
基本上关键代码引用于 buskill-app -> buskill.py
引用
import win32api
import win32con
import win32gui
import time
from ctypes import *
常量
# Device change events (WM_DEVICECHANGE wParam)
# 设备改变事件
# 除 0x8000 , 0x8001 和 0x8004 ,其他标识符一般都与 0x0007 相关
DBT_DEVNODES_CHANGED = 0x0007
DBT_DEVICEARRIVAL = 0x8000
DBT_DEVICEQUERYREMOVE = 0x8001
DBT_DEVICEQUERYREMOVEFAILED = 0x8002
DBT_DEVICEMOVEPENDING = 0x8003
DBT_DEVICEREMOVECOMPLETE = 0x8004
DBT_DEVICETYPESSPECIFIC = 0x8005
DBT_CONFIGCHANGED = 0x0018
WORD = c_ushort
DWORD = c_ulong
# 本节参考 1
class DEV_BROADCAST_HDR(Structure):
_fields_ = [
("dbch_size", DWORD),
("dbch_devicetype", DWORD),
("dbch_reserved", DWORD)
]
# 本节参考 2
class DEV_BROADCAST_VOLUME(Structure):
_fields_ = [
("dbcv_size", DWORD),
("dbcv_devicetype", DWORD),
("dbcv_reserved", DWORD),
("dbcv_unitmask", DWORD),
("dbcv_flags", WORD)
]
# 计算新添加设备索引
def drive_from_mask(mask):
n_drive = 0
while 1:
if (mask & (2 ** n_drive)):
return n_drive
else:
n_drive += 1
主要类
class Notification:
def __init__(self):
message_map = {
win32con.WM_DEVICECHANGE: self.hotplugCallbackWin
}
# 通过windows窗口类监听 USB 设备插入、删除等操作
wc = win32gui.WNDCLASS()
hinst = wc.hInstance = win32api.GetModuleHandle(None)
wc.lpszClassName = "DeviceChangeDemo"
wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
wc.hbrBackground = win32con.COLOR_WINDOW
wc.lpfnWndProc = message_map
classAtom = win32gui.RegisterClass(wc)
style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
self.hwnd = win32gui.CreateWindow(
classAtom,
"Device Change Demo",
style,
0, 0,
win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
0, 0,
hinst, None
)
# this is a callback function that is registered to be called when a usb
# hotplug event occurs in windows
# WM_DEVICECHANGE:
# wParam - type of change: arrival, removal etc.
# lParam - what's changed?
# if it's a volume then...
# lParam - what's changed more exactly
def hotplugCallbackWin(self, hwnd, message, wparam, lparam):
dev_broadcast_hdr = DEV_BROADCAST_HDR.from_address(lparam)
# USB 存储设备插入
if wparam == DBT_DEVICEARRIVAL:
loggerUSBInfo(self, hwnd, message, wparam, lparam)
# USB 存储设备拔出
elif wparam == DBT_DEVICEREMOVECOMPLETE:
loggerUSBInfo(self, hwnd, message, wparam, lparam)
elif wparam == DBT_DEVICEQUERYREMOVE:
loggerUSBInfo(self, hwnd, message, wparam, lparam)
# 设备更新相关标识符
# elif wparam == DBT_DEVNODES_CHANGED or DBT_DEVICEQUERYREMOVEFAILED or DBT_DEVICEMOVEPENDING or DBT_DEVICETYPESSPECIFIC or DBT_CONFIGCHANGED:
# loggerUSBInfo(self, hwnd, message, wparam, lparam, 1)
else:
loggerUSBInfo(self, hwnd, message, wparam, lparam, 1)
pass
return 1
信息输出方法
def loggerUSBInfo(self, hwnd, message, wparam, lparam, handshake: int = 0):
"""
打印 USB 信息
handshake: 设备添加或删除时的设备更新操作,默认 0 。
Value 0 : 非添加或删除操作,计算输出显示 USB 存储设备盘符。
Value 1 : 添加或删除操作,不计算输出显示 USB 存储设备盘符。
"""
# 时间
msg = "time:|" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "|"
loggerPrint(msg, 0, 1)
# 句柄
msg = "hwnd:|" + str(hex(hwnd)) + "|"
loggerPrint(msg, 1, 1)
# 信息
msg = "message:|" + str(hex(message)) + "|"
loggerPrint(msg, 1, 1)
# 事件
msg = "wparam:|" + WM_DEVICECHANGE.getKey(wparam) + ":" + str(hex(wparam)) + "|"
# msg = "wparam:|" + str(wparam) + "|"
loggerPrint(msg, 1, 1)
# Windows 消息
msg = "lparam:|" + str(hex(lparam)) + "|"
loggerPrint(msg, 1, 1)
# windows 设备广播数据卷
dev_broadcast_volume = DEV_BROADCAST_VOLUME.from_address(lparam)
msg = "dev_broadcast_volume:|" + str(dev_broadcast_volume) + "|"
loggerPrint(msg, 1, 1)
# 判断是否有关设备更新操作
if handshake == 0:
# 驱动器位数
drive_letter = drive_from_mask(dev_broadcast_volume.dbcv_unitmask)
msg = "drive_letter:|" + str(drive_from_mask(dev_broadcast_volume.dbcv_unitmask)) + "|"
loggerPrint(msg, 1, 1)
# 驱动器盘符
msg = "ch( ord('A') + drive_letter):|" + \
str(chr(ord('A') + drive_letter)) + '|'
loggerPrint(msg, 1, 1)
print()
def loggerPrint(msg: object, type: int = 0, outFlag: int = 1):
"""
打印信息
type:
Value 0 - no logger.
Value 1 - debug.
Value 2 - error.
outFlag:
Value 0 - not print in terminal.
Value 1 - print in terminal.
"""
if type == 0:
pass
if type == 1:
logger.debug(msg)
elif type == 2:
logger.error(msg)
if outFlag == 1:
print(msg)
开始语句
if __name__ == '__main__':
w = Notification()
win32gui.PumpMessages()