Detect media insertion on Windows in Python
Asked Answered
S

0

5

I need a program that detects media insertion and also tells me the drive letter so that I can build on it and add other functions to be run when the device inserted event is fired.

I think it can be done using WMI using the Win32_VolumeChangeEvent class (I found some implementations in Powershell and C# but I want to do it with Python). I know there is also the wmi module for python eventually and I found this snippet of code from a Python mailing list but it seems it doesn't work.

Then I also found this Python script that could do what I need. It seems it was written for python 2 and I adjusted the parenthesis for the print() function in order to make it work on python 3, besides I noticed there were a couple of unnecessary ; in the code. (maybe it was ported from C and the developer left them there by mistake. This python script uses ctypes).

I show you the code I got:

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

Windows sends all top-level windows a set of default WM_DEVICECHANGE messages when new devices or media (such as a CD or DVD) are added and become available, and when existing devices or media are removed.

Each WM_DEVICECHANGE message has an associated event that describes the change, and a structure that provides detailed information about the change. The structure consists of an event-independent header, DEV_BROADCAST_HDR, followed by event-dependent members. The event-dependent members describe the device to which the event applies. To use this structure, applications must first determine the event type and the device type. Then, they can use the correct structure to take appropriate action.

When the user inserts a new CD or DVD into a drive, applications receive a WM_DEVICECHANGE message with a DBT_DEVICEARRIVAL event. The application must check the event to ensure that the type of device arriving is a volume (the dbch_devicetype member is DBT_DEVTYP_VOLUME) and that the change affects the media (the dbcv_flags member is DBTF_MEDIA).

Here you can find an implementation in C++ directly from Microsoft MSDN.


PROBLEMS:

The code compiles without errors and if I insert a USB drive I get the message "Something's arrived" and "It's a volume!" correctly but the message "with some media" and the drive letter are never displayed so it's like this part of the code doesn't work:

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

I need to fix the program in order to know also the drive letter of the new media inserted.


UPDATE:

I tried to print the value of dev_broadcast_volume.dbcv_flags and it's 0. Then I tried to print the value of DBTF_MEDIA and it's 1. I see that in the code there is an if statement with a bitwise operation:

if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:

If both dev_broadcast_volume.dbcv_flags and DBTF_MEDIA were == 1, the bitwise operation would return 1, so the if statement would be True and the code inside would be executed but dev_broadcast_volume.dbcv_flags == 0 so the bitwise operation would return 0 and the if statement is False and the code won't get executed, right?

I tried to remove the if statement entirely and although the check doesn't exist anymore (is it necessary?), now the drive letter is printed correctly.

This is the output of the program I get now:

Something's arrived
It's a volume!
in drive K
Smtih answered 31/7, 2016 at 23:2 Comment(6)
dbcv_flags i coud say from reading this flag takes two values DBTF_NET/DBTF_MEDIA so 'if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA: can you give me what condition you are checking in this if suppose for equality have you tried this ifdev_broadcast_volume.dbcv_flags==DBTF_MEDIA:Myrick
I tried after your suggestion but it seems it still doesn't workSmtih
A USB disk mounts as a drive, not as removable media, so there's no reason in this case to check for DBTF_MEDIA.Lubricate
Note that when drives are mounted in a folder (e.g. C:\Mount\SomeDiskName), the system doesn't generate DBT_DEVTYP_VOLUME messages. Instead it requires calling RegisterDeviceNotification for the GUID_DEVINTERFACE_VOLUME device class. You still get a WM_DEVICECHANGE message, but for DBT_DEVTYP_DEVICEINTERFACE, which has one of the mount points. From there you can call GetVolumeNameForVolumeMountPoint and then GetVolumePathNamesForVolumeName to get all the mount points (drive letters and folders).Lubricate
this answered my question, but was very hard to find on google. Here's some keywords to help that for the next guy: usb hotplugging hotplug callback register registration events windows python libusb alternative #62602221Clipping
Another suggestion is using a list of parrent path to be check. After that you may write an script to check this parent folder's files and changes in a loop with a specific delay.Charolettecharon

© 2022 - 2024 — McMap. All rights reserved.