1515package gousb
1616
1717import (
18+ "errors"
1819 "fmt"
1920 "log"
2021 "reflect"
@@ -26,12 +27,23 @@ import (
2627/*
2728#cgo pkg-config: libusb-1.0
2829#include <libusb.h>
30+ #include <stdlib.h>
2931
3032int gousb_compact_iso_data(struct libusb_transfer *xfer, unsigned char *status);
3133struct libusb_transfer *gousb_alloc_transfer_and_buffer(int bufLen, int numIsoPackets);
3234void gousb_free_transfer_and_buffer(struct libusb_transfer *xfer);
3335int submit(struct libusb_transfer *xfer);
3436void gousb_set_debug(libusb_context *ctx, int lvl);
37+ int gousb_hotplug_register_callback(
38+ libusb_context* ctx,
39+ libusb_hotplug_event events,
40+ libusb_hotplug_flag flags,
41+ int vid,
42+ int pid,
43+ int dev_class,
44+ void *user_data,
45+ libusb_hotplug_callback_handle *handle
46+ );
3547*/
3648import "C"
3749
@@ -125,6 +137,8 @@ func (ep libusbEndpoint) endpointDesc(dev *DeviceDesc) EndpointDesc {
125137 return ei
126138}
127139
140+ type libusbHotplugCallback func (* libusbContext , * libusbDevice , HotplugEventType ) bool
141+
128142// libusbIntf is a set of trivial idiomatic Go wrappers around libusb C functions.
129143// The underlying code is generally not testable or difficult to test,
130144// since libusb interacts directly with the host USB stack.
@@ -166,6 +180,9 @@ type libusbIntf interface {
166180 data (* libusbTransfer ) (int , TransferStatus )
167181 free (* libusbTransfer )
168182 setIsoPacketLengths (* libusbTransfer , uint32 )
183+
184+ // hotplug
185+ registerHotplugCallback (ctx * libusbContext , events HotplugEventType , enumerate bool , vendorID int32 , productID int32 , devClass int32 , fn libusbHotplugCallback ) (func (), error )
169186}
170187
171188// libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb.
@@ -216,6 +233,17 @@ func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) {
216233
217234func (libusbImpl ) exit (c * libusbContext ) error {
218235 C .libusb_exit ((* C .libusb_context )(c ))
236+ // libusb_exit automatically deregisters hotplug callbacks,
237+ // but we need to free the callback map.
238+ hotplugCallbackMap .Lock ()
239+ if m , ok := hotplugCallbackMap .m [c ]; ok {
240+ for id := range m {
241+ delete (m , id )
242+ C .free (id )
243+ }
244+ }
245+ delete (hotplugCallbackMap .m , c )
246+ hotplugCallbackMap .Unlock ()
219247 return nil
220248}
221249
@@ -524,3 +552,90 @@ func newContextPointer() *libusbContext {
524552func newDevHandlePointer () * libusbDevHandle {
525553 return (* libusbDevHandle )(unsafe .Pointer (C .malloc (1 )))
526554}
555+
556+ // hotplugCallbackMap keeps a map of go callback functions for libusb hotplug callbacks
557+ // for each context.
558+ // When a context is closed with libusb_exit, its callbacks are automatically deregistered
559+ // by libusb, and they are removed from this map too.
560+ var hotplugCallbackMap = struct {
561+ m map [* libusbContext ]map [unsafe.Pointer ]libusbHotplugCallback
562+ sync.RWMutex
563+ }{
564+ m : make (map [* libusbContext ]map [unsafe.Pointer ]libusbHotplugCallback ),
565+ }
566+
567+ //export goHotplugCallback
568+ func goHotplugCallback (ctx * C.libusb_context , device * C.libusb_device , event C.libusb_hotplug_event , userData unsafe.Pointer ) C.int {
569+ var fn libusbHotplugCallback
570+ hotplugCallbackMap .RLock ()
571+ m , ok := hotplugCallbackMap .m [(* libusbContext )(ctx )]
572+ if ok {
573+ fn , ok = m [userData ]
574+ }
575+ hotplugCallbackMap .RUnlock ()
576+ if ! ok {
577+ // This shouldn't happen. Deregister the callback.
578+ return 1
579+ }
580+ dereg := fn ((* libusbContext )(ctx ), (* libusbDevice )(device ), HotplugEventType (event ))
581+
582+ if dereg {
583+ hotplugCallbackMap .Lock ()
584+ m , ok := hotplugCallbackMap .m [(* libusbContext )(ctx )]
585+ if ok {
586+ delete (m , userData )
587+ C .free (userData )
588+ }
589+ hotplugCallbackMap .Unlock ()
590+ return 1
591+ }
592+ return 0
593+ }
594+
595+ func (libusbImpl ) registerHotplugCallback (ctx * libusbContext , events HotplugEventType , enumerate bool , vendorID int32 , productID int32 , devClass int32 , fn libusbHotplugCallback ) (func (), error ) {
596+ // We must allocate memory to pass to C, since we can't pass a go pointer.
597+ // We can use the resulting pointer as a map key instead of
598+ // storing the map key inside the memory allocated.
599+ id := C .malloc (1 )
600+ if id == nil {
601+ panic (errors .New ("Failed to allocate memory during callback registration" ))
602+ }
603+ hotplugCallbackMap .Lock ()
604+ m , ok := hotplugCallbackMap .m [ctx ]
605+ if ! ok {
606+ hotplugCallbackMap .m [ctx ] = make (map [unsafe.Pointer ]libusbHotplugCallback )
607+ m = hotplugCallbackMap .m [ctx ]
608+ }
609+ m [id ] = fn
610+ hotplugCallbackMap .Unlock ()
611+
612+ var flags C.libusb_hotplug_flag
613+ if enumerate {
614+ flags = C .LIBUSB_HOTPLUG_ENUMERATE
615+ }
616+ var handle C.libusb_hotplug_callback_handle
617+
618+ // TODO: figure out how to run deregister in callback.
619+ // There's a race condition here, because the callback may be called before
620+ // gousb_hotplug_register_callback returns, depending on libusb's implementation.
621+ res := C .gousb_hotplug_register_callback ((* C .libusb_context )(ctx ), C .libusb_hotplug_event (events ), flags , C .int (vendorID ), C .int (productID ), C .int (devClass ), id , & handle )
622+ err := fromErrNo (res )
623+ if err != nil {
624+ hotplugCallbackMap .Lock ()
625+ delete (hotplugCallbackMap .m [ctx ], id )
626+ hotplugCallbackMap .Unlock ()
627+ C .free (id )
628+ return nil , err
629+ }
630+
631+ return func () {
632+ C .libusb_hotplug_deregister_callback ((* C .libusb_context )(ctx ), handle )
633+ hotplugCallbackMap .Lock ()
634+ m , ok := hotplugCallbackMap .m [ctx ]
635+ if ok {
636+ delete (m , id )
637+ C .free (id )
638+ }
639+ hotplugCallbackMap .Unlock ()
640+ }, nil
641+ }
0 commit comments