33package uart
44
55import (
6+ "errors"
67 "strings"
78 "unsafe"
89
@@ -12,14 +13,41 @@ import (
1213
1314// getSerialPorts returns available serial ports on Windows
1415func getSerialPorts () ([]serialPort , error ) {
15- // First, get COM ports from registry
16- comPorts , err := getRegistryCOMPorts ()
17- if err != nil {
18- return nil , err
16+ // First, try to get COM ports from registry
17+ registryPorts , registryErr := getRegistryCOMPorts ()
18+
19+ // Get COM ports from SetupAPI (more comprehensive)
20+ setupAPIPorts , setupErr := getSetupAPICOMPorts ()
21+
22+ // If both methods failed, return combined error information
23+ if registryErr != nil && setupErr != nil {
24+ return nil , errors .Join (registryErr , setupErr )
25+ }
26+
27+ // Merge ports from both sources, preferring SetupAPI data
28+ portMap := make (map [string ]serialPort )
29+
30+ // Add registry ports first
31+ if registryErr == nil {
32+ for _ , port := range registryPorts {
33+ portMap [port .Path ] = port
34+ }
1935 }
2036
21- // Then enrich with USB information using SetupAPI
22- return enrichWithUSBInfo (comPorts )
37+ // Add/overwrite with SetupAPI ports (they have more metadata)
38+ if setupErr == nil {
39+ for _ , port := range setupAPIPorts {
40+ portMap [port .Path ] = port
41+ }
42+ }
43+
44+ // Convert map back to slice
45+ ports := make ([]serialPort , 0 , len (portMap ))
46+ for _ , port := range portMap {
47+ ports = append (ports , port )
48+ }
49+
50+ return ports , nil
2351}
2452
2553// getRegistryCOMPorts gets COM ports from Windows registry
@@ -52,8 +80,8 @@ func getRegistryCOMPorts() ([]serialPort, error) {
5280 return ports , nil
5381}
5482
55- // enrichWithUSBInfo adds USB device information to COM ports
56- func enrichWithUSBInfo ( ports [] serialPort ) ([]serialPort , error ) {
83+ // getSetupAPICOMPorts gets COM ports directly from SetupAPI
84+ func getSetupAPICOMPorts ( ) ([]serialPort , error ) {
5785 // Load setupapi.dll
5886 setupapi := windows .NewLazySystemDLL ("setupapi.dll" )
5987 setupDiGetClassDevs := setupapi .NewProc ("SetupDiGetClassDevsW" )
@@ -78,16 +106,12 @@ func enrichWithUSBInfo(ports []serialPort) ([]serialPort, error) {
78106 DIGCF_PRESENT ,
79107 )
80108
81- if devInfo == 0 {
82- return ports , nil // Return original ports without enrichment
109+ if devInfo == uintptr ( windows . InvalidHandle ) {
110+ return nil , windows . GetLastError ()
83111 }
84112 defer setupDiDestroyDeviceInfoList .Call (devInfo )
85113
86- // Create a map for quick lookup
87- portMap := make (map [string ]* serialPort )
88- for i := range ports {
89- portMap [ports [i ].Path ] = & ports [i ]
90- }
114+ var ports []serialPort
91115
92116 // Enumerate devices
93117 type spDevinfoData struct {
@@ -111,27 +135,43 @@ func enrichWithUSBInfo(ports []serialPort) ([]serialPort, error) {
111135 break
112136 }
113137
114- // Get friendly name (includes COM port)
138+ // Get friendly name (includes COM port) using two-call pattern
115139 const SPDRP_FRIENDLYNAME = 0x0000000C
116- var friendlyName [256 ]uint16
117140 var propertyType uint32
118- var size uint32
141+ var requiredSize uint32
142+
143+ // First call to get the required buffer size
144+ setupDiGetDeviceRegistryProperty .Call (
145+ devInfo ,
146+ uintptr (unsafe .Pointer (& devInfoData )),
147+ SPDRP_FRIENDLYNAME ,
148+ 0 , // propertyType is optional on first call
149+ 0 , // nil buffer
150+ 0 , // buffer size 0
151+ uintptr (unsafe .Pointer (& requiredSize )),
152+ )
119153
154+ if requiredSize == 0 {
155+ continue
156+ }
157+
158+ // Allocate buffer of the required size and call again
159+ friendlyNameBuf := make ([]uint16 , requiredSize / 2 )
120160 ret , _ , _ = setupDiGetDeviceRegistryProperty .Call (
121161 devInfo ,
122162 uintptr (unsafe .Pointer (& devInfoData )),
123163 SPDRP_FRIENDLYNAME ,
124164 uintptr (unsafe .Pointer (& propertyType )),
125- uintptr (unsafe .Pointer (& friendlyName [0 ])),
126- uintptr (uint32 ( len ( friendlyName ) * 2 ) ),
127- uintptr ( unsafe . Pointer ( & size )),
165+ uintptr (unsafe .Pointer (& friendlyNameBuf [0 ])),
166+ uintptr (requiredSize ),
167+ 0 , // requiredSize is optional on second call
128168 )
129169
130170 if ret == 0 {
131171 continue
132172 }
133173
134- name := windows .UTF16ToString (friendlyName [:] )
174+ name := windows .UTF16ToString (friendlyNameBuf )
135175
136176 // Extract COM port from friendly name
137177 var comPort string
@@ -145,56 +185,89 @@ func enrichWithUSBInfo(ports []serialPort) ([]serialPort, error) {
145185 continue
146186 }
147187
148- // Find matching port
149- port , exists := portMap [ comPort ]
150- if ! exists {
151- continue
188+ // Create new port entry
189+ port := serialPort {
190+ Path : comPort ,
191+ Name : name ,
152192 }
153193
154- // Get hardware ID for VID/PID
194+ // Get hardware ID for VID/PID using two-call pattern
155195 const SPDRP_HARDWAREID = 0x00000001
156- var hardwareID [ 512 ] uint16
196+ var hwRequiredSize uint32
157197
158- ret , _ , _ = setupDiGetDeviceRegistryProperty .Call (
198+ // First call to get the required buffer size
199+ setupDiGetDeviceRegistryProperty .Call (
159200 devInfo ,
160201 uintptr (unsafe .Pointer (& devInfoData )),
161202 SPDRP_HARDWAREID ,
162- uintptr ( unsafe . Pointer ( & propertyType )),
163- uintptr ( unsafe . Pointer ( & hardwareID [ 0 ])),
164- uintptr ( uint32 ( len ( hardwareID ) * 2 )),
165- uintptr (unsafe .Pointer (& size )),
203+ 0 , // propertyType is optional on first call
204+ 0 , // nil buffer
205+ 0 , // buffer size 0
206+ uintptr (unsafe .Pointer (& hwRequiredSize )),
166207 )
167208
168- if ret != 0 {
169- hwid := windows .UTF16ToString (hardwareID [:])
170- // Parse VID/PID from hardware ID (format: USB\VID_xxxx&PID_xxxx)
171- if vidpid := parseWindowsHardwareID (hwid ); vidpid != "" {
172- port .VIDPID = vidpid
209+ if hwRequiredSize > 0 {
210+ // Allocate buffer of the required size and call again
211+ hardwareIDBuf := make ([]uint16 , hwRequiredSize / 2 )
212+ ret , _ , _ = setupDiGetDeviceRegistryProperty .Call (
213+ devInfo ,
214+ uintptr (unsafe .Pointer (& devInfoData )),
215+ SPDRP_HARDWAREID ,
216+ uintptr (unsafe .Pointer (& propertyType )),
217+ uintptr (unsafe .Pointer (& hardwareIDBuf [0 ])),
218+ uintptr (hwRequiredSize ),
219+ 0 , // hwRequiredSize is optional on second call
220+ )
221+
222+ if ret != 0 {
223+ hwid := windows .UTF16ToString (hardwareIDBuf )
224+ // Parse VID/PID from hardware ID (format: USB\VID_xxxx&PID_xxxx)
225+ if vidpid := parseWindowsHardwareID (hwid ); vidpid != "" {
226+ port .VIDPID = vidpid
227+ }
173228 }
174229 }
175230
176- // Get manufacturer
231+ // Get manufacturer using two-call pattern
177232 const SPDRP_MFG = 0x0000000B
178- var mfg [ 256 ] uint16
233+ var mfgRequiredSize uint32
179234
180- ret , _ , _ = setupDiGetDeviceRegistryProperty .Call (
235+ // First call to get the required buffer size
236+ setupDiGetDeviceRegistryProperty .Call (
181237 devInfo ,
182238 uintptr (unsafe .Pointer (& devInfoData )),
183239 SPDRP_MFG ,
184- uintptr ( unsafe . Pointer ( & propertyType )),
185- uintptr ( unsafe . Pointer ( & mfg [ 0 ])),
186- uintptr ( uint32 ( len ( mfg ) * 2 )),
187- uintptr (unsafe .Pointer (& size )),
240+ 0 , // propertyType is optional on first call
241+ 0 , // nil buffer
242+ 0 , // buffer size 0
243+ uintptr (unsafe .Pointer (& mfgRequiredSize )),
188244 )
189245
190- if ret != 0 {
191- port .Manufacturer = windows .UTF16ToString (mfg [:])
246+ if mfgRequiredSize > 0 {
247+ // Allocate buffer of the required size and call again
248+ mfgBuf := make ([]uint16 , mfgRequiredSize / 2 )
249+ ret , _ , _ = setupDiGetDeviceRegistryProperty .Call (
250+ devInfo ,
251+ uintptr (unsafe .Pointer (& devInfoData )),
252+ SPDRP_MFG ,
253+ uintptr (unsafe .Pointer (& propertyType )),
254+ uintptr (unsafe .Pointer (& mfgBuf [0 ])),
255+ uintptr (mfgRequiredSize ),
256+ 0 , // mfgRequiredSize is optional on second call
257+ )
258+
259+ if ret != 0 {
260+ port .Manufacturer = windows .UTF16ToString (mfgBuf )
261+ }
192262 }
193263
194264 // Extract product from friendly name
195265 if n := strings .Index (name , " (" ); n > 0 {
196266 port .Product = name [:n ]
197267 }
268+
269+ // Add port to results
270+ ports = append (ports , port )
198271 }
199272
200273 return ports , nil
@@ -233,7 +306,13 @@ func parseWindowsHardwareID(hwid string) string {
233306 return vid + ":" + pid
234307}
235308
236- // getSerialPortsFallback not needed on Windows as registry always works
309+ // getSerialPortsFallback provides a fallback method for COM port detection
237310func getSerialPortsFallback () ([]serialPort , error ) {
311+ // Try SetupAPI first as it's more comprehensive
312+ if ports , err := getSetupAPICOMPorts (); err == nil {
313+ return ports , nil
314+ }
315+
316+ // Fallback to registry method
238317 return getRegistryCOMPorts ()
239318}
0 commit comments