USB memory stick detection

About 

This PRG source code implements the "DriveDetector" class. Objects of this class are capable of identifying the drives currently available, and detecting when a USB memory stick is inserted or removed by the user.

The PRG code demonstrates four programming techniques available with The YUKON Project:

  • Subclassing from the XbpStaticEx class for creating an unvisible window
  • Assigning a callback code block to a Windows message (XbpStaticEx:setCallback())
  • Processing a Windows message (DriveDetector:WM_DeviceChange()
  • Declaring and using a user defined structure (DEV_BROADCAST_DEVICEINTERFACE)

A "DriveDetector" object posts the following events to Xbase++'s event queue

  • xbeP_DriveAdded
  • xbeP_DriveRemoved
Screen shot 
USBStick.prg 
// FILE: USBStick.prg
// Copyright (c) Hannes Ziegler, 2008 
// This file is published as Open Source for The YUKON Project (www.knowleXbase.com)
//
// This program shows how to detect insertion or removal of a USB memory stick in PRG code
//
// Requirements:
//   - Alaska Xbase++ version 1.9 or later
//   - The YUKON Project build 85 or later

#include "AppEvent.ch"
#include "YUkonEVM.ch"


#define  xbeP_DriveAdded              xbeP_User + 1024
#define  xbeP_DriveRemoved            xbeP_User + 1025


PROCEDURE Main
   LOCAL nEvent, mp1, mp2, oXbp, aDevInfo
   LOCAL oDriveDetect := DriveDetector():new():create()

   ? "Add or remove a USB memory stick (press ESC to quit)"
   ?

   nEvent := 0

   DO WHILE nEvent <> xbeP_Close
      nEvent := AppEventEx( @mp1, @mp2, @oXbp )

      IF oXbp <> NIL
         oXbp:HandleEvent( nEvent, mp1, mp2 )
      ENDIF

      DO CASE 
      CASE nEvent == xbeK_SPACE
         CLS

      CASE nEvent == xbeK_ESC
         EXIT

      CASE nEvent == xbeP_DriveAdded
         ? "Drive added  :", mp1 + ":"
         ? oDriveDetect:getDrives()
         ?
      CASE nEvent == xbeP_DriveRemoved
         ? "Drive removed:", mp1 + ":"
         ? oDriveDetect:getDrives()
         ?
      ENDCASE
   ENDDO

   oDriveDetect:destroy()

RETURN self


/* ***************************************************************************
 * Class monitoring available drives
 * ************************************************************************* */
CLASS DriveDetector FROM XbpStaticEx
   PROTECTED:
   CLASS VAR msgs

   VAR nDrives
   VAR oDevStruct
   VAR nHandle

   EXPORTED:

   CLASS METHOD initClass
   METHOD init, create, destroy

   METHOD getDrives, WM_DeviceChange
ENDCLASS


/* ***************************************************************************
 * Load a hash table holding values of DBT_* notification messages
 * ************************************************************************* */
CLASS METHOD DriveDetector:initClass()
   IF ::msgs == NIL
      ::msgs := WinDefines( "DBT_*" )
   ENDIF
RETURN self


/* ***************************************************************************
 * Initialize the object
 * ************************************************************************* */
METHOD DriveDetector:init( oParent )
   ::XbpStaticEx:init( oParent )

   // get bitmask of drives currently available
   ::nDrives := KERNEL32.GetLogicalDrives()

   // load the structure required for the RegisterDeviceNotification API
   ::oDevStruct := StructLoad( "DEV_BROADCAST_DEVICEINTERFACE" )
RETURN Self


/* ***************************************************************************
 * Request system resources
 * ************************************************************************* */
METHOD DriveDetector:create( oParent )
   LOCAL nFlags, nMessage
 
   // Create XbpStaticEx outside the visible area of the parent 
   // The DriveDetector window is unvisible
   ::XbpStaticEx:create( oParent, oParent, {-10,-10}, {1,1} )

   // This is the sum of two #define constants of the Windows platform SDK
   nFlags := WinDefines( "DEVICE_NOTIFY_WINDOW_HANDLE"       , ;
                         "DEVICE_NOTIFY_ALL_INTERFACE_CLASSES" )

   // Obtain a registration handle for device notifications
   ::nHandle := USER32.RegisterDeviceNotification( ::getHWND()  , ;
                                                   ::oDevStruct , ;
                                                   nFlags         ) 

   // This yields the numeric value of the WM_DEVICECHANGE #define constant
   nMessage := WinDefines( "WM_DEVICECHANGE" )

   // Register a callback code block for the WM_DEVICECHANGE message
   ::setCallback( nMessage, ;
                  {|obj,msg,wParam,lParam| obj:WM_DeviceChange( wParam, lParam ) } )
RETURN self


/* ***************************************************************************
 * Cleanup memory
 * ************************************************************************* */
METHOD DriveDetector:destroy()
   LOCAL nMsg := Windefines( "WM_DEVICECHANGE" )

   // release the callback code block
   ::setCallback( nMsg, NIL )

   // release the notification handle
   USER32.UnregisterDeviceNotification( ::nHandle  ) 
   ::nHandle := 0

   // release the structure
   kill::oDevStruct

   // release the window 
   ::XbpStaticEx:destroy()

RETURN self   


/* ***************************************************************************
 * Return drives currently available in an array
 * ************************************************************************* */
METHOD DriveDetector:getDrives()
   LOCAL nInBytes := 4096
   LOCAL nOutBytes:= 0
   LOCAL cBuffer  := ZeroStr( nInBytes )

   nOutBytes := KERNEL32.GetLogicalDriveStrings( nInBytes, @cBuffer )

RETURN SplitStr( Left(cBuffer,nOutBytes), Chr(0) )


/* ***************************************************************************
 * Process the WM_DEVICECHANGE message
 * ************************************************************************* */
METHOD DriveDetector:WM_DeviceChange( wParam, lParam )
   LOCAL lDriveAdded, nOldDrives, nNewDrives, cDrive, i

   IF wParam     == ::msgs:DBT_DEVICEARRIVAL
      lDriveAdded := .T.
   ELSEIF wParam == ::msgs:DBT_DEVICEREMOVECOMPLETE
      lDriveAdded := .F.
   ENDIF

   IF lDriveAdded == NIL
      RETURN self
   ENDIF

   nOldDrives := ::nDrives
   nNewDrives := KERNEL32.GetLogicalDrives()

   IF nOldDrives == nNewDrives
      RETURN self
   ENDIF
      
   ::oDevStruct:loadFrom( lParam )

   // Find the drive letter that has changed
   FOR i:=1 TO 32
      IF nOldDrives[i] <> nNewDrives[i]
         cDrive := Chr(64+i)
         EXIT
      ENDIF
   NEXT

   ::nDrives := nNewDrives

   IF lDriveAdded
      PostAppEvent( xbeP_DriveAdded  , cDrive, ::oDevStruct:dbcc_name, ::setParent() )
   ELSE
      PostAppEvent( xbeP_DriveRemoved, cDrive, ::oDevStruct:dbcc_name, ::setParent() )
   ENDIF
RETURN 1


/* ***************************************************************************
 * Structure required for the RegisterDeviceNotification API
 * ************************************************************************* */
STRUCTURE DEV_BROADCAST_DEVICEINTERFACE SIZEOF 540
   MEMBER dbcc_size       AT  0 IS DWORD
   MEMBER dbcc_devicetype AT  4 IS DWORD
   MEMBER dbcc_reserved   AT  8 IS DWORD
   MEMBER dbcc_classguid  AT 12 IS UUID
   MEMBER dbcc_name       AT 28 IS CHAR SIZEOF 512  // Make this large enough to receive the device name string

   DEFAULT MEMBER ;
      dbcc_size       := 540 , ;
      dbcc_devicetype := WinDefines( "DBT_DEVTYP_DEVICEINTERFACE" )
STRUCTEND