diff --git a/go.mod b/go.mod index f2ba4d7..2a8e986 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,12 @@ go 1.19 require ( github.com/arduino/go-properties-orderedmap v1.7.1 + github.com/arduino/go-win32-utils v1.0.0 github.com/arduino/pluggable-discovery-protocol-handler/v2 v2.1.1 ) require ( github.com/arduino/go-paths-helper v1.8.0 // indirect github.com/pkg/errors v0.9.1 // indirect + golang.org/x/sys v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index 3b199ee..eb924b0 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/arduino/go-properties-orderedmap v1.7.1 h1:HQ9Pn/mk3+XyfrE39EEvaZwJkr github.com/arduino/go-properties-orderedmap v1.7.1/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8= +github.com/arduino/go-win32-utils v1.0.0 h1:/cXB86sOJxOsCHP7sQmXGLkdValwJt56mIwOHYxgQjQ= +github.com/arduino/go-win32-utils v1.0.0/go.mod h1:0jqM7doGEAs6DaJCxxhLBUDS5OawrqF48HqXkcEie/Q= github.com/arduino/pluggable-discovery-protocol-handler/v2 v2.1.1 h1:MPQZ2YImq5qBiOPwTFGOrl6E99XGSRHc+UzHA6hsjvc= github.com/arduino/pluggable-discovery-protocol-handler/v2 v2.1.1/go.mod h1:2lA930B1Xu/otYT1kbx3l1n5vFJuuyPNkQaqOoQHmPE= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -472,6 +474,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.c b/main.c index a807611..e32b848 100644 --- a/main.c +++ b/main.c @@ -79,3 +79,44 @@ void dfuProbeDevices() { clearDfuRoot(); probe_devices(ctx); } + +volatile int has_event = 0; + +int libusbHandleEvents() { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 500000; + libusb_handle_events_timeout_completed(ctx, &tv, NULL); + int res = has_event; + has_event = 0; + return res; +} + +int callback(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void *user_data) { + has_event = 1; + return 0; +} + +libusb_hotplug_callback_handle callbackHandle; + +const char *libusbHotplugRegisterCallback() { + int err = libusb_hotplug_register_callback( + ctx, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + LIBUSB_HOTPLUG_NO_FLAGS, + LIBUSB_HOTPLUG_MATCH_ANY, // Vendor ID + LIBUSB_HOTPLUG_MATCH_ANY, // Product ID + LIBUSB_HOTPLUG_MATCH_ANY, // Device Class + callback, // Actual callback function + NULL, // User data + &callbackHandle + ); + if (err != 0) { + return libusb_strerror(err); + } + return NULL; +} + +void libusbHotplugDeregisterCallback() { + libusb_hotplug_deregister_callback(ctx, callbackHandle); +} diff --git a/main.go b/main.go index 7ceb0bf..af83659 100644 --- a/main.go +++ b/main.go @@ -18,8 +18,8 @@ package main /* -#cgo CPPFLAGS: -DHAVE_UNISTD_H -DHAVE_NANOSLEEP -DHAVE_ERR -I. -Idfu-util-0.11/src -I/usr/local/include/libusb-1.0 -#cgo CFLAGS: -DHAVE_UNISTD_H -DHAVE_NANOSLEEP -DHAVE_ERR -I. -Idfu-util-0.11/src -I/usr/local/include/libusb-1.0 +#cgo CPPFLAGS: -DHAVE_UNISTD_H -DHAVE_NANOSLEEP -DHAVE_ERR -I. -Idfu-util-0.11/src -I/usr/local/include/libusb-1.0 -Wall +#cgo CFLAGS: -DHAVE_UNISTD_H -DHAVE_NANOSLEEP -DHAVE_ERR -I. -Idfu-util-0.11/src -I/usr/local/include/libusb-1.0 -Wall #cgo darwin LDFLAGS: -L/usr/local/lib -lusb-1.0 -framework IOKit -framework CoreFoundation -framework Security #cgo !darwin LDFLAGS: -L/usr/local/lib -lusb-1.0 @@ -32,13 +32,16 @@ char *get_path(libusb_device *dev); const char *libusbOpen(); void libusbClose(); void dfuProbeDevices(); +const char *libusbHotplugRegisterCallback(); +void libusbHotplugDeregisterCallback(); +int libusbHandleEvents(); */ import "C" import ( + "errors" "fmt" "os" - "time" "github.com/arduino/go-properties-orderedmap" discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" @@ -55,7 +58,7 @@ func main() { // DFUDiscovery is the implementation of the DFU pluggable-discovery type DFUDiscovery struct { - closeChan chan<- struct{} + close func() portsCache map[string]*discovery.Port } @@ -71,9 +74,9 @@ func (d *DFUDiscovery) Quit() { // Stop is the handler for the pluggable-discovery STOP command func (d *DFUDiscovery) Stop() error { - if d.closeChan != nil { - close(d.closeChan) - d.closeChan = nil + if d.close != nil { + d.close() + d.close = nil } C.libusbClose() return nil @@ -81,23 +84,36 @@ func (d *DFUDiscovery) Stop() error { // StartSync is the handler for the pluggable-discovery START_SYNC command func (d *DFUDiscovery) StartSync(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) error { + d.portsCache = map[string]*discovery.Port{} if cErr := C.libusbOpen(); cErr != nil { return fmt.Errorf("can't open libusb: %s", C.GoString(cErr)) } + return d.sync(eventCB, errorCB) +} + +func (d *DFUDiscovery) libusbSync(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) error { + if err := C.libusbHotplugRegisterCallback(); err != nil { + return errors.New(C.GoString(err)) + } + closeChan := make(chan struct{}) go func() { - d.portsCache = map[string]*discovery.Port{} + d.sendUpdates(eventCB, errorCB) for { - d.sendUpdates(eventCB, errorCB) + if C.libusbHandleEvents() != 0 { + d.sendUpdates(eventCB, errorCB) + } select { - case <-time.After(5 * time.Second): case <-closeChan: - d.portsCache = nil + C.libusbHotplugDeregisterCallback() return + default: } } }() - d.closeChan = closeChan + d.close = func() { + close(closeChan) + } return nil } diff --git a/sync_unix.go b/sync_unix.go new file mode 100644 index 0000000..bc911e7 --- /dev/null +++ b/sync_unix.go @@ -0,0 +1,28 @@ +// This file is part of dfu-discovery. +// +// Copyright 2023 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//go:build !windows + +package main + +import ( + discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" +) + +func (d *DFUDiscovery) sync(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) error { + return d.libusbSync(eventCB, errorCB) +} diff --git a/sync_windows.go b/sync_windows.go new file mode 100644 index 0000000..bb21bf5 --- /dev/null +++ b/sync_windows.go @@ -0,0 +1,71 @@ +// This file is part of dfu-discovery. +// +// Copyright 2023 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package main + +import ( + "context" + "time" + + "github.com/arduino/go-win32-utils/devicenotification" + discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2" +) + +func (d *DFUDiscovery) sync(eventCB discovery.EventCallback, errorCB discovery.ErrorCallback) error { + ctx, cancel := context.WithCancel(context.Background()) + d.close = cancel + + deviceEventChan := make(chan bool, 1) + go func() { + err := devicenotification.Start(ctx, func() { + select { + case deviceEventChan <- true: + default: + } + }, errorCB) + if err != nil { + errorCB(err.Error()) + } + }() + + go func() { + d.sendUpdates(eventCB, errorCB) + + for { + select { + case <-ctx.Done(): + return + case <-deviceEventChan: + } + + again: + d.sendUpdates(eventCB, errorCB) + + // Trigger another update after 500ms because Windows might signal a + // new port much before it becomes actually available. + select { + case <-ctx.Done(): + return + case <-deviceEventChan: + goto again + case <-time.After(time.Millisecond * 500): + } + d.sendUpdates(eventCB, errorCB) + } + }() + return nil +}