1#[cfg(any(docsrs, target_os = "windows"))]
2use std::ffi::{OsStr, OsString};
3use std::fmt::Debug;
4
5#[cfg(target_os = "linux")]
6use crate::platform::SysfsPath;
7
8#[cfg(target_os = "windows")]
9use crate::platform::DevInst;
10
11#[cfg(all(docsrs, not(target_os = "windows")))]
12struct DevInst();
13
14use crate::{Device, Error, MaybeFuture};
15
16#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
18pub struct DeviceId(pub(crate) crate::platform::DeviceId);
19
20#[derive(Clone)]
31pub struct DeviceInfo {
32 #[cfg(target_os = "linux")]
33 pub(crate) path: SysfsPath,
34
35 #[cfg(any(target_os = "linux", target_os = "android"))]
36 pub(crate) busnum: u8,
37
38 #[cfg(target_os = "windows")]
39 pub(crate) instance_id: OsString,
40
41 #[cfg(target_os = "windows")]
42 pub(crate) location_paths: Vec<OsString>,
43
44 #[cfg(target_os = "windows")]
45 pub(crate) parent_instance_id: OsString,
46
47 #[cfg(target_os = "windows")]
48 pub(crate) port_number: u32,
49
50 #[cfg(target_os = "windows")]
51 pub(crate) devinst: DevInst,
52
53 #[cfg(target_os = "windows")]
54 pub(crate) driver: Option<String>,
55
56 #[cfg(target_os = "macos")]
57 pub(crate) registry_id: u64,
58
59 #[cfg(target_os = "macos")]
60 pub(crate) location_id: u32,
61
62 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows",))]
63 pub(crate) bus_id: String,
64
65 #[cfg(any(
66 target_os = "linux",
67 target_os = "macos",
68 target_os = "windows",
69 target_os = "android",
70 ))]
71 pub(crate) device_address: u8,
72
73 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
74 pub(crate) port_chain: Vec<u8>,
75
76 pub(crate) vendor_id: u16,
77 pub(crate) product_id: u16,
78
79 #[cfg(any(
80 target_os = "linux",
81 target_os = "macos",
82 target_os = "windows",
83 target_arch = "wasm32"
84 ))]
85 pub(crate) device_version: u16,
86
87 pub(crate) usb_version: u16,
88 pub(crate) class: u8,
89 pub(crate) subclass: u8,
90 pub(crate) protocol: u8,
91
92 #[cfg(any(
93 target_os = "linux",
94 target_os = "macos",
95 target_os = "windows",
96 target_os = "android",
97 ))]
98 pub(crate) speed: Option<Speed>,
99
100 pub(crate) manufacturer_string: Option<String>,
101 pub(crate) product_string: Option<String>,
102 pub(crate) serial_number: Option<String>,
103
104 pub(crate) interfaces: Vec<InterfaceInfo>,
105
106 #[cfg(target_arch = "wasm32")]
107 pub(crate) device: crate::platform::UsbDevice,
108}
109
110impl DeviceInfo {
111 pub fn id(&self) -> DeviceId {
113 #[cfg(target_os = "windows")]
114 {
115 DeviceId(self.devinst)
116 }
117
118 #[cfg(any(target_os = "linux", target_os = "android"))]
119 {
120 DeviceId(crate::platform::DeviceId {
121 bus: self.busnum,
122 addr: self.device_address,
123 })
124 }
125
126 #[cfg(target_os = "macos")]
127 {
128 DeviceId(self.registry_id)
129 }
130
131 #[cfg(target_arch = "wasm32")]
132 {
133 DeviceId(crate::platform::DeviceId::from_device(&self.device))
134 }
135 }
136
137 #[cfg(any(docsrs, target_os = "linux"))]
139 pub fn sysfs_path(&self) -> &std::path::Path {
140 &self.path.0
141 }
142
143 #[cfg(any(docsrs, target_os = "linux"))]
147 pub fn busnum(&self) -> u8 {
148 self.busnum
149 }
150
151 #[cfg(any(docsrs, target_os = "windows"))]
153 pub fn instance_id(&self) -> &OsStr {
154 &self.instance_id
155 }
156
157 #[cfg(any(docsrs, target_os = "windows"))]
159 pub fn location_paths(&self) -> &[OsString] {
160 &self.location_paths
161 }
162
163 #[cfg(any(docsrs, target_os = "windows"))]
165 pub fn parent_instance_id(&self) -> &OsStr {
166 &self.parent_instance_id
167 }
168
169 #[cfg(any(docsrs, target_os = "windows"))]
171 pub fn port_number(&self) -> u32 {
172 self.port_number
173 }
174
175 #[cfg(any(
183 docsrs,
184 target_os = "linux",
185 target_os = "macos",
186 target_os = "windows"
187 ))]
188 pub fn port_chain(&self) -> &[u8] {
189 &self.port_chain
190 }
191
192 #[cfg(any(docsrs, target_os = "windows"))]
194 pub fn driver(&self) -> Option<&str> {
195 self.driver.as_deref()
196 }
197
198 #[cfg(any(docsrs, target_os = "macos"))]
200 pub fn location_id(&self) -> u32 {
201 self.location_id
202 }
203
204 #[cfg(any(docsrs, target_os = "macos"))]
206 pub fn registry_entry_id(&self) -> u64 {
207 self.registry_id
208 }
209
210 #[cfg(any(
212 docsrs,
213 target_os = "linux",
214 target_os = "macos",
215 target_os = "windows"
216 ))]
217 pub fn bus_id(&self) -> &str {
218 &self.bus_id
219 }
220
221 #[cfg(any(
223 docsrs,
224 target_os = "linux",
225 target_os = "macos",
226 target_os = "windows"
227 ))]
228 pub fn device_address(&self) -> u8 {
229 self.device_address
230 }
231
232 #[doc(alias = "idVendor")]
234 pub fn vendor_id(&self) -> u16 {
235 self.vendor_id
236 }
237
238 #[doc(alias = "idProduct")]
240 pub fn product_id(&self) -> u16 {
241 self.product_id
242 }
243
244 #[doc(alias = "bcdDevice")]
246 #[cfg(any(
247 docsrs,
248 target_os = "linux",
249 target_os = "macos",
250 target_os = "windows",
251 target_arch = "wasm32"
252 ))]
253 pub fn device_version(&self) -> u16 {
254 self.device_version
255 }
256
257 #[doc(alias = "bcdUSB")]
259 pub fn usb_version(&self) -> u16 {
260 self.usb_version
261 }
262
263 #[doc(alias = "bDeviceClass")]
267 pub fn class(&self) -> u8 {
268 self.class
269 }
270
271 #[doc(alias = "bDeviceSubClass")]
273 pub fn subclass(&self) -> u8 {
274 self.subclass
275 }
276
277 #[doc(alias = "bDeviceProtocol")]
279 pub fn protocol(&self) -> u8 {
280 self.protocol
281 }
282
283 #[cfg(any(
285 docsrs,
286 target_os = "linux",
287 target_os = "macos",
288 target_os = "windows"
289 ))]
290 pub fn speed(&self) -> Option<Speed> {
291 self.speed
292 }
293
294 #[doc(alias = "iManufacturer")]
300 pub fn manufacturer_string(&self) -> Option<&str> {
301 self.manufacturer_string.as_deref()
302 }
303
304 #[doc(alias = "iProduct")]
306 pub fn product_string(&self) -> Option<&str> {
307 self.product_string.as_deref()
308 }
309
310 #[doc(alias = "iSerial")]
312 pub fn serial_number(&self) -> Option<&str> {
313 self.serial_number.as_deref()
314 }
315
316 pub fn interfaces(&self) -> impl Iterator<Item = &InterfaceInfo> {
335 self.interfaces.iter()
336 }
337
338 pub fn matches(&self, s: &DeviceSelector) -> bool {
340 s.vendor_id.is_none_or(|id| self.vendor_id == id)
341 && s.product_id.is_none_or(|id| self.product_id == id)
342 && s.serial_number
343 .as_ref()
344 .is_none_or(|s| self.serial_number.as_deref() == Some(s))
345 && ((s.class.is_none_or(|c| self.class == c)
346 && s.subclass.is_none_or(|s| self.subclass == s)
347 && s.protocol.is_none_or(|p| self.protocol == p))
348 || self.interfaces().any(|i| {
349 s.class.is_none_or(|c| i.class == c)
350 && s.subclass.is_none_or(|s| i.subclass == s)
351 && s.protocol.is_none_or(|p| i.protocol == p)
352 }))
353 }
354
355 pub fn open(&self) -> impl MaybeFuture<Output = Result<Device, Error>> {
357 Device::open(self)
358 }
359}
360
361impl std::fmt::Debug for DeviceInfo {
363 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
364 let mut s = f.debug_struct("DeviceInfo");
365
366 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
367 s.field("bus_id", &self.bus_id)
368 .field("device_address", &self.device_address)
369 .field("port_chain", &format_args!("{:?}", self.port_chain))
370 .field("speed", &self.speed);
371
372 s.field("vendor_id", &format_args!("0x{:04X}", self.vendor_id))
373 .field("product_id", &format_args!("0x{:04X}", self.product_id));
374
375 #[cfg(any(
376 target_os = "linux",
377 target_os = "macos",
378 target_os = "windows",
379 target_arch = "wasm32"
380 ))]
381 s.field(
382 "device_version",
383 &format_args!("0x{:04X}", self.device_version),
384 );
385
386 s.field("usb_version", &format_args!("0x{:04X}", self.usb_version))
387 .field("class", &format_args!("0x{:02X}", self.class))
388 .field("subclass", &format_args!("0x{:02X}", self.subclass))
389 .field("protocol", &format_args!("0x{:02X}", self.protocol))
390 .field("manufacturer_string", &self.manufacturer_string)
391 .field("product_string", &self.product_string)
392 .field("serial_number", &self.serial_number);
393
394 #[cfg(target_os = "linux")]
395 {
396 s.field("sysfs_path", &self.path);
397 }
398
399 #[cfg(target_os = "windows")]
400 {
401 s.field("instance_id", &self.instance_id);
402 s.field("parent_instance_id", &self.parent_instance_id);
403 s.field("location_paths", &self.location_paths);
404 s.field("port_number", &self.port_number);
405 s.field("driver", &self.driver);
406 }
407
408 #[cfg(target_os = "macos")]
409 {
410 s.field("location_id", &format_args!("0x{:08X}", self.location_id));
411 s.field(
412 "registry_entry_id",
413 &format_args!("0x{:08X}", self.registry_id),
414 );
415 }
416
417 s.field("interfaces", &self.interfaces);
418
419 s.finish()
420 }
421}
422
423#[derive(Copy, Clone, Eq, PartialOrd, Ord, PartialEq, Hash, Debug)]
425#[non_exhaustive]
426pub enum Speed {
427 Low,
429
430 Full,
432
433 High,
435
436 Super,
438
439 SuperPlus,
441}
442
443impl Speed {
444 #[allow(dead_code)] pub(crate) fn from_str(s: &str) -> Option<Self> {
446 match s {
447 "low" | "1.5" => Some(Speed::Low),
448 "full" | "12" => Some(Speed::Full),
449 "high" | "480" => Some(Speed::High),
450 "super" | "5000" => Some(Speed::Super),
451 "super+" | "10000" => Some(Speed::SuperPlus),
452 _ => None,
453 }
454 }
455}
456
457#[derive(Clone)]
459pub struct InterfaceInfo {
460 pub(crate) interface_number: u8,
461 pub(crate) class: u8,
462 pub(crate) subclass: u8,
463 pub(crate) protocol: u8,
464 pub(crate) interface_string: Option<String>,
465}
466
467impl InterfaceInfo {
468 pub fn interface_number(&self) -> u8 {
470 self.interface_number
471 }
472
473 pub fn class(&self) -> u8 {
475 self.class
476 }
477
478 pub fn subclass(&self) -> u8 {
480 self.subclass
481 }
482
483 pub fn protocol(&self) -> u8 {
485 self.protocol
486 }
487
488 pub fn interface_string(&self) -> Option<&str> {
490 self.interface_string.as_deref()
491 }
492}
493
494impl std::fmt::Debug for InterfaceInfo {
496 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
497 f.debug_struct("InterfaceInfo")
498 .field("interface_number", &self.interface_number)
499 .field("class", &format_args!("0x{:02X}", self.class))
500 .field("subclass", &format_args!("0x{:02X}", self.subclass))
501 .field("protocol", &format_args!("0x{:02X}", self.protocol))
502 .field("interface_string", &self.interface_string)
503 .finish()
504 }
505}
506
507#[derive(Copy, Clone, Eq, PartialOrd, Ord, PartialEq, Hash, Debug)]
509#[non_exhaustive]
510pub enum UsbControllerType {
511 XHCI,
513
514 EHCI,
516
517 OHCI,
519
520 UHCI,
522
523 VHCI,
525}
526
527impl UsbControllerType {
528 #[allow(dead_code)] pub(crate) fn from_str(s: &str) -> Option<Self> {
530 let lower_s = s.to_owned().to_ascii_lowercase();
531 match lower_s
532 .find("hci")
533 .filter(|i| *i > 0)
534 .and_then(|i| lower_s.as_bytes().get(i - 1))
535 {
536 Some(b'x') => Some(UsbControllerType::XHCI),
537 Some(b'e') => Some(UsbControllerType::EHCI),
538 Some(b'o') => Some(UsbControllerType::OHCI),
539 Some(b'v') => Some(UsbControllerType::VHCI),
540 Some(b'u') => Some(UsbControllerType::UHCI),
541 _ => None,
542 }
543 }
544}
545
546#[cfg(any(
553 docsrs,
554 target_os = "linux",
555 target_os = "macos",
556 target_os = "windows"
557))]
558pub struct BusInfo {
559 #[cfg(any(target_os = "linux"))]
560 pub(crate) path: SysfsPath,
561
562 #[cfg(any(target_os = "linux"))]
564 pub(crate) root_hub: DeviceInfo,
565
566 #[cfg(any(target_os = "linux"))]
567 pub(crate) busnum: u8,
568
569 #[cfg(target_os = "windows")]
570 pub(crate) instance_id: OsString,
571
572 #[cfg(target_os = "windows")]
573 pub(crate) location_paths: Vec<OsString>,
574
575 #[cfg(target_os = "windows")]
576 pub(crate) devinst: DevInst,
577
578 #[cfg(target_os = "windows")]
579 pub(crate) root_hub_description: String,
580
581 #[cfg(target_os = "windows")]
582 pub(crate) parent_instance_id: OsString,
583
584 #[cfg(target_os = "macos")]
585 pub(crate) registry_id: u64,
586
587 #[cfg(target_os = "macos")]
588 pub(crate) location_id: u32,
589
590 #[cfg(target_os = "macos")]
591 pub(crate) provider_class_name: String,
592
593 #[cfg(target_os = "macos")]
594 pub(crate) class_name: String,
595
596 #[cfg(target_os = "macos")]
597 pub(crate) name: Option<String>,
598
599 pub(crate) driver: Option<String>,
600
601 pub(crate) bus_id: String,
603
604 pub(crate) controller_type: Option<UsbControllerType>,
606}
607
608#[cfg(any(
609 docsrs,
610 target_os = "linux",
611 target_os = "macos",
612 target_os = "windows"
613))]
614impl BusInfo {
615 #[cfg(any(docsrs, target_os = "linux"))]
617 pub fn sysfs_path(&self) -> &std::path::Path {
618 &self.path.0
619 }
620
621 #[cfg(any(docsrs, target_os = "linux"))]
625 pub fn busnum(&self) -> u8 {
626 self.busnum
627 }
628
629 #[cfg(any(docsrs, target_os = "linux"))]
631 pub fn root_hub(&self) -> &DeviceInfo {
632 &self.root_hub
633 }
634
635 #[cfg(any(docsrs, target_os = "windows"))]
637 pub fn instance_id(&self) -> &OsStr {
638 &self.instance_id
639 }
640
641 #[cfg(any(docsrs, target_os = "windows"))]
643 pub fn parent_instance_id(&self) -> &OsStr {
644 &self.parent_instance_id
645 }
646
647 #[cfg(any(docsrs, target_os = "windows"))]
649 pub fn location_paths(&self) -> &[OsString] {
650 &self.location_paths
651 }
652
653 #[cfg(any(docsrs, target_os = "windows"))]
655 pub fn devinst(&self) -> DevInst {
656 self.devinst
657 }
658
659 #[cfg(any(docsrs, target_os = "macos"))]
661 pub fn location_id(&self) -> u32 {
662 self.location_id
663 }
664
665 #[cfg(any(docsrs, target_os = "macos"))]
667 pub fn registry_entry_id(&self) -> u64 {
668 self.registry_id
669 }
670
671 #[cfg(any(docsrs, target_os = "macos"))]
673 pub fn provider_class_name(&self) -> &str {
674 &self.provider_class_name
675 }
676
677 #[cfg(any(docsrs, target_os = "macos"))]
679 pub fn class_name(&self) -> &str {
680 &self.class_name
681 }
682
683 #[cfg(any(docsrs, target_os = "macos"))]
685 pub fn name(&self) -> Option<&str> {
686 self.name.as_deref()
687 }
688
689 pub fn driver(&self) -> Option<&str> {
691 self.driver.as_deref()
692 }
693
694 pub fn bus_id(&self) -> &str {
696 &self.bus_id
697 }
698
699 pub fn controller_type(&self) -> Option<UsbControllerType> {
703 self.controller_type
704 }
705
706 pub fn system_name(&self) -> Option<&str> {
714 #[cfg(any(target_os = "linux"))]
715 {
716 self.root_hub.product_string()
717 }
718
719 #[cfg(target_os = "windows")]
720 {
721 Some(&self.root_hub_description)
722 }
723
724 #[cfg(target_os = "macos")]
725 {
726 self.name.as_deref()
727 }
728
729 #[cfg(target_arch = "wasm32")]
730 {
731 None
732 }
733 }
734}
735
736#[cfg(any(
737 docsrs,
738 target_os = "linux",
739 target_os = "macos",
740 target_os = "windows"
741))]
742impl std::fmt::Debug for BusInfo {
743 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
744 let mut s = f.debug_struct("BusInfo");
745
746 #[cfg(any(target_os = "linux"))]
747 {
748 s.field("sysfs_path", &self.path);
749 s.field("busnum", &self.busnum);
750 }
751
752 #[cfg(target_os = "windows")]
753 {
754 s.field("instance_id", &self.instance_id);
755 s.field("parent_instance_id", &self.parent_instance_id);
756 s.field("location_paths", &self.location_paths);
757 }
758
759 #[cfg(target_os = "macos")]
760 {
761 s.field("location_id", &format_args!("0x{:08X}", self.location_id));
762 s.field(
763 "registry_entry_id",
764 &format_args!("0x{:08X}", self.registry_id),
765 );
766 s.field("class_name", &self.class_name);
767 s.field("provider_class_name", &self.provider_class_name);
768 }
769
770 s.field("bus_id", &self.bus_id)
771 .field("system_name", &self.system_name())
772 .field("controller_type", &self.controller_type)
773 .field("driver", &self.driver);
774
775 s.finish()
776 }
777}
778
779#[derive(Default, Clone)]
792pub struct DeviceSelector {
793 pub(crate) vendor_id: Option<u16>,
794 pub(crate) product_id: Option<u16>,
795 pub(crate) class: Option<u8>,
796 pub(crate) subclass: Option<u8>,
797 pub(crate) protocol: Option<u8>,
798 pub(crate) serial_number: Option<String>,
799}
800
801impl DeviceSelector {
802 pub const fn all() -> DeviceSelector {
804 DeviceSelector {
805 vendor_id: None,
806 product_id: None,
807 class: None,
808 subclass: None,
809 protocol: None,
810 serial_number: None,
811 }
812 }
813
814 pub const fn with_vid(mut self, vendor_id: u16) -> DeviceSelector {
816 self.vendor_id = Some(vendor_id);
817 self
818 }
819
820 pub const fn with_vid_pid(mut self, vendor_id: u16, product_id: u16) -> DeviceSelector {
822 self.vendor_id = Some(vendor_id);
823 self.product_id = Some(product_id);
824 self
825 }
826
827 pub const fn with_class(mut self, class_code: u8) -> DeviceSelector {
829 self.class = Some(class_code);
830 self
831 }
832
833 pub const fn with_class_subclass(
835 mut self,
836 class_code: u8,
837 subclass_code: u8,
838 ) -> DeviceSelector {
839 self.class = Some(class_code);
840 self.subclass = Some(subclass_code);
841 self
842 }
843
844 pub const fn with_class_subclass_protocol(
846 mut self,
847 class_code: u8,
848 subclass_code: u8,
849 protocol_code: u8,
850 ) -> DeviceSelector {
851 self.class = Some(class_code);
852 self.subclass = Some(subclass_code);
853 self.protocol = Some(protocol_code);
854 self
855 }
856
857 pub fn with_serial_number(mut self, serial_number: String) -> DeviceSelector {
859 self.serial_number = Some(serial_number);
860 self
861 }
862
863 pub fn vendor_id(&self) -> Option<u16> {
865 self.vendor_id
866 }
867
868 pub fn product_id(&self) -> Option<u16> {
870 self.product_id
871 }
872
873 pub fn class(&self) -> Option<u8> {
875 self.class
876 }
877
878 pub fn subclass(&self) -> Option<u8> {
880 self.subclass
881 }
882
883 pub fn protocol(&self) -> Option<u8> {
885 self.protocol
886 }
887
888 pub fn serial_number(&self) -> Option<&str> {
890 self.serial_number.as_deref()
891 }
892}
893
894impl Debug for DeviceSelector {
895 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
896 let DeviceSelector {
897 vendor_id,
898 product_id,
899 class,
900 subclass,
901 protocol,
902 ref serial_number,
903 } = *self;
904
905 let mut s = f.debug_struct("DeviceSelector");
906 if let Some(vendor_id) = vendor_id {
907 s.field("vendor_id", &format_args!("0x{vendor_id:04X}"));
908 }
909 if let Some(product_id) = product_id {
910 s.field("product_id", &format_args!("0x{product_id:04X}"));
911 }
912 if let Some(class) = class {
913 s.field("class", &format_args!("0x{class:02X}"));
914 }
915 if let Some(subclass) = subclass {
916 s.field("subclass", &format_args!("0x{subclass:02X}"));
917 }
918 if let Some(protocol) = protocol {
919 s.field("protocol", &format_args!("0x{protocol:02X}"));
920 }
921 if let Some(ref serial_number) = serial_number {
922 s.field("serial_number", serial_number);
923 }
924 s.finish()
925 }
926}