-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdevice_manager.rs
More file actions
116 lines (104 loc) · 5.05 KB
/
Copy pathdevice_manager.rs
File metadata and controls
116 lines (104 loc) · 5.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// SPDX-License-Identifier: GPL-3.0-or-later
//! USB device discovery for Framework Laptop 16 accessories.
//!
//! Enumerates the system USB bus via `rusb` (libusb) and filters for
//! Framework's vendor ID, then maps known product IDs to a friendly
//! name/category. Exposes a single Tauri command, [`scan_devices`], used
//! by the frontend to populate the device list. VID/PID values here are
//! hardware-specific to the Framework Laptop 16 and its expansion card
//! ecosystem — see the comment above [`identify_device`] for how each
//! entry was confirmed.
use framework_lib::audio_card::AUDIO_CARD_PID;
use serde::Serialize;
use std::sync::Mutex;
const FRAMEWORK_VID: u16 = 0x32AC;
/// A USB device on the bus that matches Framework's vendor ID, with a
/// best-effort friendly name and category attached by [`identify_device`].
#[derive(Serialize, Debug, Clone)]
pub struct ConnectedDevice {
pub vid: u16,
pub pid: u16,
pub description: String,
pub device_type: String, // "Keyboard", "Matrix", "Expansion", "Unknown"
}
/// Remembers the (vid, pid) set from the previous [`scan_devices`] call so
/// it only logs when that set actually changes, instead of on every poll —
/// the frontend (`Sidebar.tsx`) calls this every 5 seconds just to keep the
/// connection pill live, which made the console scroll constantly even when
/// nothing was happening.
#[derive(Default)]
pub struct DeviceScanState {
last_signature: Mutex<Option<Vec<(u16, u16)>>>,
}
// Maps a Framework-VID device's PID to a friendly name/type. Entries below
// are PIDs confirmed against real hardware (Windows Device Manager +, for
// the Matrix, a live serial round-trip — see matrix_control.rs) or sourced
// directly from FrameworkComputer/framework-system's own constants (the
// Audio Card PID). This replaces an earlier guessed PID-range scheme that
// had never been verified and silently misclassified the LED Matrix
// (0x0020) as a Numpad.
//
// Only Expansion Cards with their own chip/firmware (HDMI/DisplayPort,
// Audio) enumerate as distinct USB devices at all — confirmed against
// framework-system's own feature list, which only offers firmware-info
// commands for those two. Plain USB-A/USB-C/SD/Storage/Ethernet cards are
// passive pass-throughs with no identity of their own; nothing shows up
// for them until something is plugged into the port, and even then it's
// the plugged-in peripheral's PID, not the card's. Numpad/Macropad PIDs
// are also still unconfirmed — both intentionally fall through to
// "Unknown" rather than guess again.
/// Maps a Framework-VID device's PID to a `(friendly_name, category)` pair.
/// Falls through to `"Unknown"` for anything not explicitly confirmed
/// rather than guessing.
fn identify_device(pid: u16) -> (String, String) {
match pid {
0x0012 => ("Framework Laptop 16 Keyboard".to_string(), "Keyboard".to_string()),
0x0020 => ("Framework Laptop 16 LED Matrix".to_string(), "Matrix".to_string()),
0x0002 => ("HDMI Expansion Card".to_string(), "Expansion".to_string()),
pid if pid == AUDIO_CARD_PID => ("Audio Expansion Card".to_string(), "Expansion".to_string()),
_ => (format!("Unknown Device ({:04x})", pid), "Unknown".to_string()),
}
}
/// Scans the USB bus for connected Framework devices and returns them
/// classified by PID via [`identify_device`]. Only logs to the console when
/// the set of connected devices actually changes since the last call — see
/// [`DeviceScanState`].
///
/// # Errors
/// Returns an error string if the USB bus itself can't be accessed
/// (e.g. missing libusb backend); individual device descriptor read
/// failures are silently skipped rather than failing the whole scan.
#[tauri::command]
pub fn scan_devices(state: tauri::State<DeviceScanState>) -> Result<Vec<ConnectedDevice>, String> {
let mut found_devices = Vec::new();
if let Ok(devices) = rusb::devices() {
for device in devices.iter() {
if let Ok(desc) = device.device_descriptor() {
if desc.vendor_id() == FRAMEWORK_VID {
let (description, device_type) = identify_device(desc.product_id());
found_devices.push(ConnectedDevice {
vid: desc.vendor_id(),
pid: desc.product_id(),
description,
device_type,
});
}
}
}
} else {
return Err("Failed to access USB bus".to_string());
}
let mut signature: Vec<(u16, u16)> = found_devices.iter().map(|d| (d.vid, d.pid)).collect();
signature.sort_unstable();
let mut last_signature = state.last_signature.lock().map_err(|e| e.to_string())?;
if last_signature.as_ref() != Some(&signature) {
println!(
"Framework devices changed: {} device(s) now connected (VID {:04x}): {:?}",
found_devices.len(),
FRAMEWORK_VID,
found_devices.iter().map(|d| d.description.as_str()).collect::<Vec<_>>()
);
*last_signature = Some(signature);
}
Ok(found_devices)
}