Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check for device change (add/remove) events

Tags:

c#

.net

windows

I'd like to know if there's a way to trigger an event when a device is added or removed from the system. I want to be able to detect if say, a USB flash drive has been added, or a mouse, or whatever else. I tried searching around, but I can't find anything that say's how to do this.

Any ideas?

like image 326
Andi Jay Avatar asked Apr 26 '13 21:04

Andi Jay


3 Answers

If you have a window in your application, you can use something like this:

using System;
using System.Runtime.InteropServices;

internal static class UsbNotification
{
    public const int DbtDevicearrival = 0x8000; // system detected a new device        
    public const int DbtDeviceremovecomplete = 0x8004; // device is gone      
    public const int WmDevicechange = 0x0219; // device change event      
    private const int DbtDevtypDeviceinterface = 5;
    private static readonly Guid GuidDevinterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); // USB devices
    private static IntPtr notificationHandle;

    /// <summary>
    /// Registers a window to receive notifications when USB devices are plugged or unplugged.
    /// </summary>
    /// <param name="windowHandle">Handle to the window receiving notifications.</param>
    public static void RegisterUsbDeviceNotification(IntPtr windowHandle)
    {
        DevBroadcastDeviceinterface dbi = new DevBroadcastDeviceinterface
        {
            DeviceType = DbtDevtypDeviceinterface,
            Reserved = 0,
            ClassGuid = GuidDevinterfaceUSBDevice,
            Name = 0
        };

        dbi.Size = Marshal.SizeOf(dbi);
        IntPtr buffer = Marshal.AllocHGlobal(dbi.Size);
        Marshal.StructureToPtr(dbi, buffer, true);

        notificationHandle = RegisterDeviceNotification(windowHandle, buffer, 0);
    }

    /// <summary>
    /// Unregisters the window for USB device notifications
    /// </summary>
    public static void UnregisterUsbDeviceNotification()
    {
        UnregisterDeviceNotification(notificationHandle);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);

    [DllImport("user32.dll")]
    private static extern bool UnregisterDeviceNotification(IntPtr handle);

    [StructLayout(LayoutKind.Sequential)]
    private struct DevBroadcastDeviceinterface
    {
        internal int Size;
        internal int DeviceType;
        internal int Reserved;
        internal Guid ClassGuid;
        internal short Name;
    }
}

Here's how you use it from a WPF Window (Windows Forms is similar):

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        // Adds the windows message processing hook and registers USB device add/removal notification.
        HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        if (source != null)
        {
            windowHandle = source.Handle;
            source.AddHook(HwndHandler);
            UsbNotification.RegisterUsbDeviceNotification(windowHandle);
        }
    }

    /// <summary>
    /// Method that receives window messages.
    /// </summary>
    private IntPtr HwndHandler(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
    {
        if (msg == UsbNotification.WmDevicechange)
        {
            switch ((int)wparam)
            {
                case UsbNotification.DbtDeviceremovecomplete:
                    Usb_DeviceRemoved(); // this is where you do your magic
                    break;
                case UsbNotification.DbtDevicearrival:
                    Usb_DeviceAdded(); // this is where you do your magic
                    break;
            }
        }

        handled = false;
        return IntPtr.Zero;
    }

Here's the use example for Windows Forms (even simpler):

public Form1()
{
    InitializeComponent();
    UsbNotification.RegisterUsbDeviceNotification(this.Handle);
}

protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);
        if (m.Msg == UsbNotification.WmDevicechange)
    {
        switch ((int)m.WParam)
        {
            case UsbNotification.DbtDeviceremovecomplete:
                Usb_DeviceRemoved(); // this is where you do your magic
                break;
            case UsbNotification.DbtDevicearrival:
                Usb_DeviceAdded(); // this is where you do your magic
                break;
        }
    }
}   
like image 106
Darko Kenda Avatar answered Oct 06 '22 21:10

Darko Kenda


The accepted answer is excellent, however it only works with USB devices.

To make it work with all devices (and optionally filter USB), use the following slightly modified class:

static class DeviceNotification {
    //https://msdn.microsoft.com/en-us/library/aa363480(v=vs.85).aspx
    public const int DbtDeviceArrival = 0x8000; // system detected a new device        
    public const int DbtDeviceRemoveComplete = 0x8004; // device is gone     
    public const int DbtDevNodesChanged = 0x0007; //A device has been added to or removed from the system.

    public const int WmDevicechange = 0x0219; // device change event      
    private const int DbtDevtypDeviceinterface = 5;
    //https://msdn.microsoft.com/en-us/library/aa363431(v=vs.85).aspx
    private const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4;
    private static readonly Guid GuidDevinterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); // USB devices
    private static IntPtr notificationHandle;

    /// <summary>
    /// Registers a window to receive notifications when devices are plugged or unplugged.
    /// </summary>
    /// <param name="windowHandle">Handle to the window receiving notifications.</param>
    /// <param name="usbOnly">true to filter to USB devices only, false to be notified for all devices.</param>
    public static void RegisterDeviceNotification(IntPtr windowHandle, bool usbOnly = false) {
        var dbi = new DevBroadcastDeviceinterface {
            DeviceType = DbtDevtypDeviceinterface,
            Reserved = 0,
            ClassGuid = GuidDevinterfaceUSBDevice,
            Name = 0
        };

        dbi.Size = Marshal.SizeOf(dbi);
        IntPtr buffer = Marshal.AllocHGlobal(dbi.Size);
        Marshal.StructureToPtr(dbi, buffer, true);

        notificationHandle = RegisterDeviceNotification(windowHandle, buffer, usbOnly ? 0 : DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
    }

    /// <summary>
    /// Unregisters the window for device notifications
    /// </summary>
    public static void UnregisterDeviceNotification() {
        UnregisterDeviceNotification(notificationHandle);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);

    [DllImport("user32.dll")]
    private static extern bool UnregisterDeviceNotification(IntPtr handle);

    [StructLayout(LayoutKind.Sequential)]
    private struct DevBroadcastDeviceinterface {
        internal int Size;
        internal int DeviceType;
        internal int Reserved;
        internal Guid ClassGuid;
        internal short Name;
    }
}

The key change is the Flags parameter when calling RegisterDeviceNotification (see https://msdn.microsoft.com/en-us/library/aa363431(v=vs.85).aspx), which if set to 4 instead of 0 will ignore the ClassGuid parameter and register for all devices.

like image 40
Erwin Mayer Avatar answered Oct 06 '22 23:10

Erwin Mayer


I came to this post for a more specific case than the original question in that I want to be notified anytime a port is added or removed. For this situation the answer is much more simple and does not require calling RegisterDeviceNotification:

The DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE events are automatically broadcast to all top-level windows for port devices. Therefore, it is not necessary to call RegisterDeviceNotification for ports....

https://docs.microsoft.com/en-us/windows/desktop/api/Winuser/nf-winuser-registerdevicenotificationa

So the solution becomes something like:

using System.Runtime.InteropServices;

//Put all of the following code inside your Form's partial class:

private struct DEV_BROADCAST_HDR {
    internal UInt32 dbch_size;
    internal UInt32 dbch_devicetype;
    internal UInt32 dbch_reserved;
};

protected override void WndProc(ref Message m) {
    base.WndProc(ref m);        //This allows window default behavior of base class to be executed
    if (m.Msg == 0x0219) {      //WM_DEVICECHANGE = 0x0219
        DEV_BROADCAST_HDR dbh;
        switch ((int)m.WParam) {                    
            case 0x8000:        //DBT_DEVICEARRIVAL = 0x8000
                dbh = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
                if (dbh.dbch_devicetype == 0x00000003) {     //DBT_DEVTYP_PORT = 0x00000003
                    Console.WriteLine("Port added!");
                    //TODO
                }
                break;
            case 0x8004:        //DBT_DEVICEREMOVECOMPLETE = 0x8004                     
                dbh = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
                if (dbh.dbch_devicetype == 0x00000003) {     //DBT_DEVTYP_PORT = 0x00000003
                    Console.WriteLine("Port removed!");
                    //TODO
                }
                break;
        }
    }
}
like image 25
DatuPuti Avatar answered Oct 06 '22 23:10

DatuPuti