WM_DEVICECHANGE & SHCNE_DISKEVENTS
by Pierre Bellisle
#COMPILE EXE '#Win# #DIM ALL #INCLUDE "Win32api.inc" '#RESOURCE ".pbr" GLOBAL hDlg AS DWORD %Edit = 101 %WM_DEVICECHANGE = &H219& 'Notifies application of change to hardware configuration of device or computer. %DBT_CONFIGCHANGECANCELED = &H19& 'Request to change the current configuration (dock or undock) has been canceled. %DBT_CONFIGCHANGED = &H18& 'Current configuration changed, due to a dock or undock. %DBT_CUSTOMEVENT = &H8006& 'Custom event. %DBT_DEVICEARRIVAL = &H8000& 'Device or piece of media has been inserted and is now available. %DBT_DEVICEQUERYREMOVE = &H8001& 'Permission requested to remove Device or media. (Any application can deny this request and cancel the removal.) %DBT_DEVICEQUERYREMOVEFAILED = &H8002& 'Request to remove device or media canceled. %DBT_DEVICEREMOVECOMPLETE = &H8004& 'Device or Media removed. %DBT_DEVICEREMOVEPENDING = &H8003& 'Device or Media about to be removed. Cannot be denied. %DBT_DEVICETYPESPECIFIC = &H8005& 'Device-specific event. %DBT_DEVNODES_CHANGED = &H7& 'Device added or removed from the system. %DBT_QUERYCHANGECONFIG = &H17& 'Permission requested to change current configuration, dock or undock. %DBT_USERDEFINED = &HFFFF& 'User-Defined. %DBTF_MEDIA = 1 'Media comings and goings %DBTF_NET = 2 'Network volume %DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = &H4& 'All Interfaces. %DEVICE_NOTIFY_WINDOW_HANDLE = 0 'Notifications sent using WM_POWERBROADCAST with wParam = PBT_POWERSETTINGCHANGE. %DEVICE_NOTIFY_SERVICE_HANDLE = 1 'Notifications sent to HandlerEx callback function with dwControl = SERVICE_CONTROL_POWEREVENT and dwEventType = PBT_POWERSETTINGCHANGE. %GWL_WNDPROC = (-4) 'Sets new address for the window procedure. Windows NT/2000/XP: Can not change attribute if window does not belong to same process as alling thread. %UNSAFE_REMOVE = &H1C&'Unsafe removal of device. %SHCNRF_INTERRUPTLEVEL = &H0001??? %SHCNRF_SHELLLEVEL = &H0002??? %SHCNRF_RECURSIVEINTERRUPT = &H1000??? %SHCNRF_NEWDELIVERY = &H8000??? %DBT_DEVTYP_OEM = &H0& 'Original Equipment Manufacturer(OEM) or Independent Hardware Vendor(IHV) defined device type. It's a DEV_BROADCAST_OEM structure. %DBT_DEVTYP_DEVNODE = &H1& 'Devnode number, specific to Windows 95. %DBT_DEVTYP_VOLUME = &H2& 'Logical volume. This structure is a DEV_BROADCAST_VOLUME structure. %DBT_DEVTYP_PORT = &H3& 'Port device, serial or parallel. It's a DEV_BROADCAST_PORT structure. %DBT_DEVTYP_NET = &H4& 'Network Resource UNC. %DBT_DEVTYP_DEVICEINTERFACE = &H5& 'Class of devices. It's a DEV_BROADCAST_DEVICEINTERFACE structure. %DBT_DEVTYP_HANDLE = &H6& 'File system handle. It's a DEV_BROADCAST_HANDLE structure. TYPE DEV_BROADCAST_HDR DWORD DbhSize AS DWORD 'Size of Structure. If User-Defined event, this must be size of header, plus size of variable-length data in a DEV_BROADCAST_USERDEFINED structure. DbhDeviceType AS DWORD 'Device Type, could be interface, handle, OEM, port or Volume. DbhReserved AS DWORD 'Reserved. END TYPE TYPE DEV_BROADCAST_DEVICEINTERFACE DWORD DbDiSize AS DWORD 'Size of Structure. DbDiDeviceType AS DWORD 'Set to %DBT_DEVTYP_DEVICEINTERFACE to get Interface Type. DbDiReserved AS DWORD 'Reserved. DbDiClassguid AS GUID 'GUID for Interface Device Class. DbDiName AS ASCIIZ * 1 'When returned through %WM_DEVICECHANGE message, converted to ANSI as appropriate. END TYPE TYPE DEV_BROADCAST_HANDLE DWORD DbhSize AS DWORD 'Size of Structure. DbhDeviceType AS DWORD 'Set to %DBT_DEVTYP_HANDLE. DbhReserved AS DWORD 'Reserved. DbhHandle AS DWORD 'Handle in RegisterDeviceNotification function. DbhHdevNotify AS DWORD 'Handle to device notification, returned by the RegisterDeviceNotification. DbhEventGuid AS GUID 'Custom event GUID Valid only for DBT_CUSTOMEVENT. DbhNameOffset AS LONG 'The offset to event name. Valid only for DBT_CUSTOMEVENT. DbhData(0) AS BYTE 'Optional binary data. Valid only for DBT_CUSTOMEVENT. END TYPE TYPE DEV_BROADCAST_OEM DWORD DboSize AS DWORD 'Size of Structure. DboDeviceType AS DWORD 'Set to %DBT_DEVTYPE_OEM. DboReserved AS DWORD 'Reserved. DboIdentifier AS DWORD 'OEM-specific identifier for the device. DboSuppfunc AS DWORD 'OEM-specific function value. Possible values depend on the device. END TYPE TYPE DEV_BROADCAST_VOLUME DWORD Dbvsize AS DWORD 'Size of Structure. DbvDeviceType AS DWORD 'Set to %DBT_DEVTYP_VOLUME. DbvReserved AS DWORD 'Reserved. DbvUnitmask AS DWORD 'Logical drive, bit 0 is A:, bit 1 is B:... DbvFlags AS WORD 'If %DBTF_MEDIA, change affects media in drive else change affects physical device or drive. %DBTF_NET = network volume. END TYPE TYPE DEV_BROADCAST_PORT DWORD DbpSize AS DWORD 'Size of Structure. DbpDeviceType AS DWORD 'Set to %DBT_DEVTYP_PORT - Set to check if a port device, see DEV_BROADCAST_DEVICEINTERFACE to find interface. DbpReserved AS DWORD 'Reserved. DbpName AS ASCIIZ * %MAX_PATH 'Device friendly name such as "Com1" and "Standard 28800 bps Modem", etc. END TYPE TYPE SHChangeNotifyEntry BYTE pidl AS ITEMIDLIST POINTER fRecursive AS LONG END TYPE #IF %PB_REVISION < &H1000 DECLARE FUNCTION SHChangeNotifyRegister LIB "SHELL32.DLL" ALIAS "SHChangeNotifyRegister" _ (BYVAL HWND AS DWORD, BYVAL fSources AS LONG, BYVAL fEvents AS LONG, BYVAL wMsg AS DWORD, _ BYVAL cEntries AS LONG, BYREF ChangeNotifyEntry AS SHChangeNotifyEntry) AS DWORD DECLARE FUNCTION SHChangeNotifyDeregister LIB "SHELL32.DLL" ALIAS "SHChangeNotifyDeregister"(BYVAL ulID AS DWORD) AS LONG #ENDIF '______________________________________________________________________________ SUB TextAdd(BYVAL sText AS STRING) 'Move the caret to the end of text. SendDlgItemMessage(hDlg, %Edit, %EM_SETSEL, -2, -2) sText = sText & $CRLF 'Add a CRLF if needed 'Insert the string at caret position. SendDlgItemMessage(hDlg, %Edit, %EM_REPLACESEL, %TRUE, BYVAL STRPTR(sText)) END SUB '______________________________________________________________________________ FUNCTION DeviceEventGet(BYVAL wParam AS DWORD, BYVAL pDevBroadCastHeader AS DEV_BROADCAST_HDR POINTER)AS STRING LOCAL pDevBroadcastDeviceinterface AS DEV_BROADCAST_DEVICEINTERFACE POINTER LOCAL pDevBroadcastHandle AS DEV_BROADCAST_HANDLE POINTER LOCAL pDevBroadcastOem AS DEV_BROADCAST_OEM POINTER LOCAL pDevBroadcastPorts AS DEV_BROADCAST_PORT POINTER LOCAL pDevBroadcastVolume AS DEV_BROADCAST_VOLUME POINTER #IF %PB_REVISION < &H1000 LOCAL pzDbDiName AS ASCIIZ POINTER #ELSE LOCAL pzDbDiName AS WSTRINGZ POINTER #ENDIF LOCAL sMessage AS STRING LOCAL sDriveName AS STRING LOCAL sDriveFlags AS STRING LOCAL sManufacturer AS STRING LOCAL sProduct AS STRING LOCAL sRevision AS STRING LOCAL sDiskSerial AS STRING LOCAL sTry AS STRING LOCAL Looper AS LONG LOCAL CharPos1 AS LONG LOCAL CharPos2 AS LONG SELECT CASE @pDevBroadCastHeader.DbhDevicetype CASE %DBT_DEVTYP_DEVICEINTERFACE 'DEV_BROADCAST_DEVICEINTERFACE - Device's class pDevBroadcastDeviceinterface = pDevBroadCastHeader pzDbDiName = pDevBroadcastDeviceinterface + 28 sMessage = "DBT_DEVTYP_DEVICEINTERFACE - GUID: " & _ GUIDTXT$(@pDevBroadcastDeviceinterface.DbDiClassguid) & _ " - Interface name: " & @pzDbDiName sManufacturer = "None" sProduct = sManufacturer sRevision = sManufacturer sDiskSerial = sManufacturer sTry = LCASE$(@pzDbDiName) CharPos1 = INSTR(sTry, "&ven_" ) IF CharPos1 THEN CharPos2 = INSTR(CharPos1 ,sTry, "&prod_") IF CharPos2 THEN CharPos1 = CharPos1 + 5 'LEN("&ven_") IF (CharPos2 - CharPos1) THEN sManufacturer = MID$(@pzDbDiName, CharPos1, CharPos2 - CharPos1) CharPos1 = INSTR(CharPos2, sTry, "&rev_") IF CharPos1 THEN CharPos2 = CharPos2 + 6 'LEN("&prod_") IF (CharPos1 - CharPos2) THEN sProduct = MID$(@pzDbDiName, CharPos2, CharPos1 - CharPos2) CharPos2 = INSTR(CharPos1, sTry, "#") IF CharPos2 THEN CharPos1 = CharPos1 + 5 'LEN("&rev_") IF (CharPos2 - CharPos1) THEN sRevision = MID$(@pzDbDiName, CharPos1, CharPos2 - CharPos1) CharPos1 = INSTR(CharPos2, sTry, "&0#") IF CharPos1 THEN sDiskSerial = MID$(@pzDbDiName, CharPos2 + 1, CharPos1 - CharPos2 - 1) sMessage = sMessage & $CRLF & $TAB & $TAB & $TAB & "Manufacturer: " & sManufacturer & _ ", product: " & sProduct & ", revision: " & sRevision & ", serial number: " & sDiskSerial END IF END IF END IF END IF END IF CASE %DBT_DEVTYP_HANDLE 'DEV_BROADCAST_HANDLE - File system handle. pDevBroadcastHandle = pDevBroadCastHeader sMessage = "DBT_DEVTYP_HANDLE - Handle: " & STR$(@pDevBroadcastHandle.DbhHandle) & _ " - Notify: " & STR$(@pDevBroadcastHandle.DbhHdevNotify) & _ IIF$((wParam = %DBT_CUSTOMEVENT), _ " - Guid: " & GUIDTXT$(@pDevBroadcastHandle.DbhEventGuid) & _ 'Valid only for DBT_CUSTOMEVENT " - Name offset: " & STR$(@pDevBroadcastHandle.DbhNameOffset)& _ 'Valid only for DBT_CUSTOMEVENT " - Data: " & STR$(@pDevBroadcastHandle.DbhData(0)), "") 'Valid only for DBT_CUSTOMEVENT CASE %DBT_DEVTYP_OEM 'DEV_BROADCAST_OEM - OEM or IHV device. pDevBroadcastOem = pDevBroadCastHeader sMessage = "DBT_DEVTYP_OEM - Identifier: " & STR$(@pDevBroadcastOem.DboIdentifier) & _ " - SuppFunc: " & STR$(@pDevBroadcastOem.DboSuppFunc) CASE %DBT_DEVTYP_PORT 'DEV_BROADCAST_PORT - Port device like serial or parallel. pDevBroadcastPorts = pDevBroadCastHeader sMessage = "DBT_DEVTYP_PORT - Port name: " & @pDevBroadcastPorts.DbpName CASE %DBT_DEVTYP_VOLUME 'DEV_BROADCAST_VOLUME - Logical volume. pDevBroadcastVolume = pDevBroadCastHeader FOR Looper = 0 TO 25 IF BIT(@pDevBroadcastVolume.DbvUnitMask, Looper) THEN sDriveName = CHR$(65 + Looper) & ":" '65 = "A" EXIT FOR END IF NEXT SELECT CASE @pDevBroadcastVolume.dbvFlags CASE 0 : sDriveFlags = "Local Drive" 'Local Volume CASE %DBTF_MEDIA : sDriveFlags = "Media Drive" 'Media Volume CASE %DBTF_NET : sDriveFlags = "Network Drive" 'Network volume END SELECT sMessage = "DBT_DEVTYP_VOLUME - Drive name: " & sDriveName & " - Flag: " & sDriveFlags sMessage = sMessage & $CRLF & $TAB & $TAB & $TAB & "Drive letter: " & sDriveName & $SPC & sDriveFlags END SELECT FUNCTION = sMessage END FUNCTION '______________________________________________________________________________ CALLBACK FUNCTION DlgProc() LOCAL sMessage AS STRING LOCAL zPath1 AS ASCIIZ * %MAX_PATH LOCAL zPath2 AS ASCIIZ * %MAX_PATH LOCAL ppIdList AS DWORD POINTER 'ITEMIDLIST POINTER ~POINTER LOCAL pIdList1 AS DWORD POINTER 'ITEMIDLIST POINTER LOCAL pIdList2 AS DWORD POINTER 'ITEMIDLIST POINTER STATIC SelStart AS LONG STATIC SelEnd AS LONG SELECT CASE AS LONG CBMSG CASE %WM_INITDIALOG PostMessage(GetDlgItem(hDlg, %Edit), %EM_SETSEL, -2, -2) CASE %WM_COMMAND SELECT CASE LOWRD(CBWPARAM) CASE %Edit IF (CBCTLMSG = %EN_KILLFOCUS) THEN SendMessage(CBLPARAM, %EM_GETSEL, VARPTR(SelStart), VARPTR(SelEnd)) END IF IF (CBCTLMSG = %EN_SETFOCUS) THEN SendMessage(CBLPARAM, %EM_SETSEL, SelStart, SelEnd) END IF END SELECT CASE %WM_DEVICECHANGE 'wParam = Event, lParam = Data SELECT CASE CBWPARAM 'Event CASE %DBT_CONFIGCHANGECANCELED sMessage = "DBT_CONFIGCHANGECANCELED" CASE %DBT_CONFIGCHANGED sMessage = "DBT_CONFIGCHANGED" CASE %DBT_CUSTOMEVENT sMessage = "DBT_CUSTOMEVENT - " & DeviceEventGet(CBWPARAM, CBLPARAM) CASE %DBT_DEVICEARRIVAL sMessage = "DBT_DEVICEARRIVAL - " & DeviceEventGet(CBWPARAM, CBLPARAM) CASE %DBT_DEVICEQUERYREMOVE sMessage = "DBT_DEVICEQUERYREMOVE - " & DeviceEventGet(CBWPARAM, CBLPARAM) CASE %DBT_DEVICEQUERYREMOVEFAILED sMessage = "DBT_DEVICEQUERYREMOVEFAILED - " & DeviceEventGet(CBWPARAM, CBLPARAM) CASE %DBT_DEVICEREMOVECOMPLETE sMessage = "DBT_DEVICEREMOVECOMPLETE - " & DeviceEventGet(CBWPARAM, CBLPARAM) CASE %DBT_DEVICEREMOVEPENDING sMessage = "DBT_DEVICEREMOVEPENDING" & DeviceEventGet(CBWPARAM, CBLPARAM) CASE %DBT_DEVICETYPESPECIFIC sMessage = "DBT_DEVICETYPESPECIFIC - " & DeviceEventGet(CBWPARAM, CBLPARAM) CASE %DBT_DEVNODES_CHANGED sMessage = "DBT_DEVNODES_CHANGED" CASE %DBT_QUERYCHANGECONFIG sMessage = "DBT_QUERYCHANGECONFIG" CASE %DBT_USERDEFINED sMessage = "DBT_USERDEFINED - " & DeviceEventGet(CBWPARAM, CBLPARAM) CASE ELSE sMessage = "DBT_UNKOWN - " & DeviceEventGet(CBWPARAM, CBLPARAM) END SELECT TextAdd(sMessage) CASE %WM_APP 'For media like memory card in a card reader and also for drive like a USB stick - SHChangeNotifyRegister SHCNE_DISKEVENTS notification 'wParam is a pointer to two PIDLIST_ABSOLUTE pointers that can be NULL, depending on the event 'lParam is the event #IF %PB_REVISION < &H1000 ppIdList = CBWPARAM pIdList1 = @ppIdList ppIdList = ppIdList + 4 pIdList2 = @ppIdList #ELSE pIdList1 = CBWPARAM pIdList2 = pIdList1 + 4 #ENDIF IF pIdList1 THEN SHGetPathFromIDList(@pIdList1, zPath1) IF pIdList2 THEN SHGetPathFromIDList(@pIdList2, zPath2) ELSE zPath2 = "Nil" ELSE zPath1 = "Nil" zPath2 = zPath1 END IF SELECT CASE CBLPARAM CASE %SHCNE_MEDIAINSERTED : sMessage = "SHCNE_MEDIAINSERTED - Media inserted: Drive letter is " & zPath1 CASE %SHCNE_MEDIAREMOVED : sMessage = "SHCNE_MEDIAREMOVED - Media removed: Drive letter is " & zPath1 CASE %SHCNE_DRIVEREMOVED : sMessage = "SHCNE_DRIVEREMOVED - Drive removed: Drive letter is " & zPath1 CASE %SHCNE_DRIVEADD : sMessage = "SHCNE_DRIVEADD - Drive added: Drive letter is " & zPath1 END SELECT TextAdd(sMessage) CASE %WM_SIZE 'wParam = flag, LOWORD(lParam) = Width, HIWORD(lParam) = height IF CBWPARAM <> %SIZE_MINIMIZED THEN MoveWindow(GetDlgItem(hDlg, %Edit), 5, 5, LO(WORD, CBLPARAM) - 10, HI(WORD, CBLPARAM) - 10, %TRUE) END IF END SELECT END FUNCTION '______________________________________________________________________________ FUNCTION PBMAIN() LOCAL NotificationFilter AS DEV_BROADCAST_DEVICEINTERFACE LOCAL hDevNotify AS DWORD LOCAL ulID AS DWORD LOCAL ppidl AS DWORD LOCAL cEntries AS LONG DIALOG NEW %HWND_DESKTOP, "WM_DEVICECHANGE & SHCNE_DISKEVENTS - Insert or remove USB device or media", , , _ 500, 150, %WS_POPUP OR %WS_BORDER OR %WS_DLGFRAME OR %WS_THICKFRAME OR %WS_CAPTION OR %WS_SYSMENU OR _ %WS_MINIMIZEBOX OR %WS_MAXIMIZEBOX OR %WS_CLIPSIBLINGS OR %WS_VISIBLE OR %DS_MODALFRAME OR %DS_3DLOOK OR _ %DS_NOFAILCREATE OR %DS_SETFONT, %WS_EX_CONTROLPARENT OR %WS_EX_LEFT OR %WS_EX_LTRREADING OR %WS_EX_RIGHTSCROLLBAR, TO hDlg SetClassLong(hDlg, %GCL_HICON, ExtractIcon(GetModuleHandle(""), "Shell32.dll", 243)) 'Set a dialog icon CONTROL ADD TEXTBOX, hDlg, %Edit, "Insert or remove card reader media, USB device..." & $CRLF, _ 5, 5, 940, 225, %WS_CHILD OR %WS_VISIBLE OR %WS_TABSTOP OR %WS_HSCROLL OR %WS_VSCROLL OR %ES_LEFT OR _ %ES_MULTILINE OR %ES_AUTOHSCROLL OR %ES_AUTOVSCROLL OR %ES_WANTRETURN OR %ES_NOHIDESEL, _ %WS_EX_CLIENTEDGE OR %WS_EX_LEFT OR %WS_EX_RIGHTSCROLLBAR OR %WS_EX_LTRREADING 'DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE are automatically broadcast for port devices. 'Volume notifications are also broadcast to top-level windows, 'so not necessary to RegisterDeviceNotification for DBT_DEVTYP_PORT OR DBT_DEVTYP_VOLUME OR DBT_DEVTYP_OEM NotificationFilter.DbDiSize = SIZEOF(DEV_BROADCAST_DEVICEINTERFACE) NotificationFilter.DbDiDevicetype = %DBT_DEVTYP_DEVICEINTERFACE hDevNotify = RegisterDeviceNotification(hDlg, BYVAL VARPTR(NotificationFilter), _ %DEVICE_NOTIFY_WINDOW_HANDLE OR _ %DEVICE_NOTIFY_ALL_INTERFACE_CLASSES) IF hDevNotify = 0 THEN MessageBox(%HWND_DESKTOP, "RegisterDeviceNotification failed!", "WM_DEVICECHANGE & SHCNE_DISKEVENTS", %MB_OK OR %MB_TOPMOST) END IF '%DBT_DEVTYP_OEM = &H0& 'Original Equipment Manufacturer(OEM) or Independent Hardware Vendor(IHV) defined device type. This structure is a DEV_BROADCAST_OEM structure. '%DBT_DEVTYP_DEVNODE = &H1& 'Devnode number (specific to Windows 95). '%DBT_DEVTYP_VOLUME = &H2& 'Logical volume. This structure is a DEV_BROADCAST_VOLUME structure. '%DBT_DEVTYP_PORT = &H3& 'Port device (serial or parallel). This structure is a DEV_BROADCAST_PORT structure. '%DBT_DEVTYP_NET = &H4& 'Network Resource (UNC). '%DBT_DEVTYP_DEVICEINTERFACE = &H5& 'Class of devices. This structure is a DEV_BROADCAST_DEVICEINTERFACE structure. '%DBT_DEVTYP_HANDLE = &H6& 'File system handle. This structure is a DEV_BROADCAST_HANDLE structure. cEntries = 1 DIM pshcne(0 TO cEntries - 1) AS SHChangeNotifyEntry 'Must be only one element when calling SHChangeNotifyRegister SHGetSpecialFolderLocation(hDlg, %CSIDL_DESKTOP, ppidl) pshcne(0).pidl = ppidl pshcne(0).fRecursive = %FALSE 'For media like memory card in a card reader and also for drive like a USB stick ulID = SHChangeNotifyRegister(BYVAL hDlg, %SHCNRF_SHELLLEVEL, _ %SHCNE_DRIVEADD OR %SHCNE_DRIVEREMOVED OR _ %SHCNE_MEDIAINSERTED OR %SHCNE_MEDIAREMOVED, _ %WM_APP, BYVAL cEntries, BYVAL VARPTR(pshcne(0))) IF ulID = 0 THEN MessageBox(%HWND_DESKTOP, "SHChangeNotifyRegister failed!", "WM_DEVICECHANGE & SHCNE_DISKEVENTS", %MB_OK OR %MB_TOPMOST) END IF DIALOG SHOW MODAL hDlg, CALL DlgProc IF ulID THEN SHChangeNotifyDeregister(ulID) 'A dword value that specifies the registration ID returned by SHChangeNotifyRegister. IF hDevNotify THEN UnregisterDeviceNotification(hDevNotify) END FUNCTION '______________________________________________________________________________ '