Detect media insertion on Windows in Python
<p>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.</p>

<p>I think it can be done using WMI using the <a href="https://msdn.microsoft.com/en-us/library/aa394516(v=vs.85).aspx" rel="nofollow">Win32_VolumeChangeEvent class</a> (I found some implementations in Powershell and C# but I want to do it with Python). I know there is also the <code>wmi</code> module for python eventually and I found <a href="https://mail.python.org/pipermail/python-win32/2010-February/010100.html" rel="nofollow">this</a> snippet of code from a Python mailing list but it seems it doesn't work.</p>

<p>Then I also found <a href="http://timgolden.me.uk/python/win32_how_do_i/detect-device-insertion.html" rel="nofollow">this</a> 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 <code>;</code> in the code. (maybe it was ported from C and the developer left them there by mistake. This python script uses ctypes).</p>

<p>I show you the code I got:</p>

<pre><code>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 &amp; (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 &amp; 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()
</code></pre>

<p>Windows sends all top-level windows a set of default <strong>WM_DEVICECHANGE</strong> 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.</p>

<p>Each <strong>WM_DEVICECHANGE</strong> 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, <strong>DEV_BROADCAST_HDR</strong>, 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.</p>

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

<p><a href="https://msdn.microsoft.com/it-it/library/windows/desktop/aa363215(v=vs.85).aspx" rel="nofollow">Here</a> you can find an implementation in C++ directly from Microsoft MSDN.</p>

<hr>

<p><strong>PROBLEMS:</strong></p>

<p>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:</p>

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

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

<hr>

<p><strong>UPDATE:</strong></p>

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

<pre><code>if dev_broadcast_volume.dbcv_flags &amp; DBTF_MEDIA:
</code></pre>

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

<p>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.</p>

<p>This is the output of the program I get now:</p>

<pre><code>Something's arrived
It's a volume!
in drive K
</code></pre>