unit USBDetectLinux;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Unix, BaseUnix, CTypes;
type
TUSBEventMethod = procedure(Arrival: Boolean; const DevicePath: String) of object;
TUSBEventProc = procedure(Arrival: Boolean; const DevicePath: String);
{ TUdevMonitorThread }
TUdevMonitorThread = class(TThread)
private
FUdev: Pointer;
FMonitor: Pointer;
FOnUSBEventMethod: TUSBEventMethod;
FOnUSBEventProc: TUSBEventProc;
procedure HandleDeviceEvent(dev: Pointer);
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
property OnUSBEvent: TUSBEventMethod read FOnUSBEventMethod write FOnUSBEventMethod;
property OnUSBEventProc: TUSBEventProc read FOnUSBEventProc write FOnUSBEventProc;
end;
implementation
const
libudev = 'libudev.so.1';
type
Pudev = Pointer;
Pudev_device = Pointer;
Pudev_monitor = Pointer;
// Udev functions
function udev_new: Pudev; cdecl; external libudev;
function udev_unref(udev: Pudev): Pudev; cdecl; external libudev;
function udev_monitor_new_from_netlink(udev: Pudev; name: PChar): Pudev_monitor; cdecl; external libudev;
function udev_monitor_filter_add_match_subsystem_devtype(mon: Pudev_monitor; subsystem: PChar; devtype: PChar): cint; cdecl; external libudev;
function udev_monitor_enable_receiving(mon: Pudev_monitor): cint; cdecl; external libudev;
function udev_monitor_get_fd(mon: Pudev_monitor): cint; cdecl; external libudev;
function udev_monitor_receive_device(mon: Pudev_monitor): Pudev_device; cdecl; external libudev;
function udev_device_get_devnode(dev: Pudev_device): PChar; cdecl; external libudev;
function udev_device_get_action(dev: Pudev_device): PChar; cdecl; external libudev;
function udev_device_get_property_value(dev: Pudev_device; key: PChar): PChar; cdecl; external libudev;
function udev_device_get_subsystem(dev: Pudev_device): PChar; cdecl; external libudev;
function udev_device_unref(dev: Pudev_device): Pudev_device; cdecl; external libudev;
{ TUdevMonitorThread }
constructor TUdevMonitorThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := False;
FUdev := udev_new();
if not Assigned(FUdev) then
raise Exception.Create('Failed to initialize udev');
end;
destructor TUdevMonitorThread.Destroy;
begin
if Assigned(FMonitor) then
udev_unref(FMonitor);
if Assigned(FUdev) then
udev_unref(FUdev);
inherited Destroy;
end;
procedure TUdevMonitorThread.HandleDeviceEvent(dev: Pointer);
var
action, devnode, subsystem, idBus: PChar;
isUSB, isSerial: Boolean;
begin
if not Assigned(dev) then Exit;
action := udev_device_get_action(dev);
devnode := udev_device_get_devnode(dev);
if not (Assigned(action) and Assigned(devnode)) then Exit;
subsystem := udev_device_get_subsystem(dev);
idBus := udev_device_get_property_value(dev, 'ID_BUS');
// Check if it's a USB device
isUSB := (idBus <> nil) and (idBus = 'usb');
// Check if it's a serial device (FIXED: added missing parenthesis)
isSerial := (subsystem <> nil) and (subsystem = 'tty') and
(udev_device_get_property_value(dev, 'ID_USB_INTERFACE_NUM') <> nil);
if isUSB and isSerial then
begin
if action = 'add' then
begin
if Assigned(FOnUSBEventMethod) then FOnUSBEventMethod(True, devnode);
if Assigned(FOnUSBEventProc) then FOnUSBEventProc(True, devnode);
end
else if action = 'remove' then
begin
if Assigned(FOnUSBEventMethod) then FOnUSBEventMethod(False, devnode);
if Assigned(FOnUSBEventProc) then FOnUSBEventProc(False, devnode);
end;
end;
// Free the device reference
udev_device_unref(dev);
end;
procedure TUdevMonitorThread.Execute;
var
fd: cint;
fds: TFDSet;
res: cint;
dev: Pudev_device;
timeout: TTimeVal;
begin
FMonitor := udev_monitor_new_from_netlink(FUdev, 'udev');
if not Assigned(FMonitor) then Exit;
// Filter for USB devices and tty subsystem
udev_monitor_filter_add_match_subsystem_devtype(FMonitor, 'usb', nil);
udev_monitor_filter_add_match_subsystem_devtype(FMonitor, 'tty', nil);
if udev_monitor_enable_receiving(FMonitor) <> 0 then
begin
udev_unref(FMonitor);
FMonitor := nil;
Exit;
end;
fd := udev_monitor_get_fd(FMonitor);
while not Terminated do
begin
fpFD_ZERO(fds);
fpFD_SET(fd, fds);
// 1 second timeout
timeout.tv_sec := 1;
timeout.tv_usec := 0;
res := fpSelect(fd + 1, @fds, nil, nil, @timeout);
// Handle errors (including EINTR)
if res < 0 then
begin
if fpgeterrno = ESysEINTR then
Continue
else
Break;
end;
if res = 0 then Continue; // Timeout
if fpFD_ISSET(fd, fds) <> 0 then
begin
dev := udev_monitor_receive_device(FMonitor);
if Assigned(dev) then
begin
HandleDeviceEvent(dev);
end;
end;
end;
if Assigned(FMonitor) then
begin
udev_unref(FMonitor);
FMonitor := nil;
end;
end;
end.