在 Python 中检测 Windows 上的媒体插入

Fab*_*bio 5 python windows usb-drive

我需要一个程序来检测媒体插入并告诉我驱动器号,以便我可以在其基础上进行构建并添加在触发设备插入事件时要运行的其他功能。

我认为可以使用Win32_VolumeChangeEvent 类使用 WMI 来完成(我在 Powershell 和 C# 中找到了一些实现,但我想使用 Python 来完成)。我知道最终还有wmipython 模块,我从 Python 邮件列表中找到了这段代码,但它似乎不起作用。

然后我还发现这个Python脚本可以满足我的需要。它似乎是为 python 2 编写的,我调整了 print() 函数的括号,以便使其在 python 3 上工作,此外我注意到;代码中有一些不必要的内容。(也许它是从 C 移植的,开发人员错误地将它们留在那里。这个 python 脚本使用 ctypes)。

我向你展示我得到的代码:

import win32api, win32con, win32gui
from ctypes import *

#
# Device change events (WM_DEVICECHANGE wParam)
#
DBT_DEVICEARRIVAL = 0x8000
DBT_DEVICEQUERYREMOVE = 0x8001
DBT_DEVICEQUERYREMOVEFAILED = 0x8002
DBT_DEVICEMOVEPENDING = 0x8003
DBT_DEVICEREMOVECOMPLETE = 0x8004
DBT_DEVICETYPESSPECIFIC = 0x8005
DBT_CONFIGCHANGED = 0x0018

#
# type of device in DEV_BROADCAST_HDR
#
DBT_DEVTYP_OEM = 0x00000000
DBT_DEVTYP_DEVNODE = 0x00000001
DBT_DEVTYP_VOLUME = 0x00000002
DBT_DEVTYPE_PORT = 0x00000003
DBT_DEVTYPE_NET = 0x00000004

#
# media types in DBT_DEVTYP_VOLUME
#
DBTF_MEDIA = 0x0001
DBTF_NET = 0x0002

WORD = c_ushort
DWORD = c_ulong


class DEV_BROADCAST_HDR(Structure):
    _fields_ = [
        ("dbch_size", DWORD),
        ("dbch_devicetype", DWORD),
        ("dbch_reserved", DWORD)
    ]


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.onDeviceChange
        }

        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
        )

    def onDeviceChange(self, hwnd, msg, wparam, lparam):
        #
        # 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
        #
        dev_broadcast_hdr = DEV_BROADCAST_HDR.from_address(lparam)

        if wparam == DBT_DEVICEARRIVAL:
            print("Something's arrived")

            if dev_broadcast_hdr.dbch_devicetype == DBT_DEVTYP_VOLUME:
                print("It's a volume!")

                dev_broadcast_volume = DEV_BROADCAST_VOLUME.from_address(lparam)
                if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:
                    print("with some media")
                    drive_letter = drive_from_mask(dev_broadcast_volume.dbcv_unitmask)
                    print("in drive", chr(ord("A") + drive_letter))

        return 1


if __name__ == '__main__':
    w = Notification()
    win32gui.PumpMessages()
Run Code Online (Sandbox Code Playgroud)

当添加新设备或媒体(例如 CD 或 DVD)并变得可用时,以及当现有设备或媒体被删除时,Windows 向所有顶级窗口发送一组默认的WM_DEVICECHANGE消息。

每个WM_DEVICECHANGE消息都有一个描述更改的关联事件以及提供有关更改的详细信息的结构。该结构由与事件无关的标头DEV_BROADCAST_HDR和后跟与事件相关的成员组成。事件相关成员描述事件适用的设备。要使用此结构,应用程序必须首先确定事件类型和设备类型。然后,他们可以使用正确的结构来采取适当的行动。

当用户将新的 CD 或 DVD 插入驱动器时,应用程序会收到带有DBT_DEVICEARRIVAL事件的WM_DEVICECHANGE消息。应用程序必须检查该事件以确保到达的设备类型是卷(dbch_devicetype成员是DBT_DEVTYP_VOLUME)并且更改会影响介质(dbcv_flags成员是DBTF_MEDIA)。

在这里您可以直接从 Microsoft MSDN 找到 C++ 实现。


问题:

代码编译时没有错误,如果我插入 USB 驱动器,我会收到消息“有东西到了”和“这是一个卷!” 正确,但消息“带有某些媒体”和驱动器号永远不会显示,因此这部分代码不起作用:

dev_broadcast_volume = DEV_BROADCAST_VOLUME.from_address(lparam)
                if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:
                    print("with some media")
                    drive_letter = drive_from_mask(dev_broadcast_volume.dbcv_unitmask)
                    print("in drive", chr(ord("A") + drive_letter))
Run Code Online (Sandbox Code Playgroud)

我需要修复程序才能知道插入的新媒体的驱动器号。


更新:

我尝试打印dev_broadcast_volume.dbcv_flags的值,它是0然后我尝试打印DBTF_MEDIA的值,它是1。我看到代码中有一个带有按位运算的 if 语句:

if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:
Run Code Online (Sandbox Code Playgroud)

如果dev_broadcast_volume.dbcv_flagsDBTF_MEDIA都== 1,则按位运算将返回1,因此 if 语句将为True,并且将执行其中的代码,但dev_broadcast_volume.dbcv_flags == 0因此按位运算将返回0和 if语句为False并且代码不会被执行,对吗?

我尝试完全删除 if 语句,尽管检查不再存在(有必要吗?),但现在驱动器号已正确打印。

这是我现在得到的程序的输出:

Something's arrived
It's a volume!
in drive K
Run Code Online (Sandbox Code Playgroud)