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()

参考

posted @ 2022-06-18 21:06  Yogile  阅读(1565)  评论(0编辑  收藏  举报