2 unstable releases
| new 0.6.0 | May 14, 2026 |
|---|---|
| 0.5.1 | May 10, 2026 |
#164 in Unix APIs
220KB
4.5K
SLoC
usbeehive 🐝
What can this USB cable actually do?
A command-line tool, and a Rust library, that tell you in plain English what each USB device plugged into your Linux machine can actually do.
usbeehive is a Linux port of WhatCable, a macOS menu-bar app by Darryl Morley. This port covers all USB devices, not just USB-C, while preserving the rich USB-C Power Delivery diagnostics from the original.
The Rust rewrite is forked from Zetaphor/whatcable-linux (originally C++ / CMake).
Renamed from
whatcable. This project was previously published on crates.io aswhatcable— at the original author's request, it has been renamed tousbeehiveas of 0.5.0. The old crate is retired (its final release is a redirect to here). Usecargo install usbeehivegoing forward.
What it shows
All USB devices
- Identity: vendor, product name, serial number
- Speed: negotiated link speed (1.5 Mbps to 20 Gbps)
- USB version: 1.1, 2.0, 3.0, 3.1, 3.2
- Power draw: how much power the device is consuming
- Device type: HID, Audio, Mass Storage, Hub, etc.
- Driver: which kernel driver is handling the device
- Topology: hub hierarchy showing what's plugged into what
USB-C ports (additional detail)
- Port roles: data role (host/device), power role (source/sink)
- Cable e-marker info: cable speed capability, current rating (3A/5A), active vs passive, cable vendor
- Charger PDO list: every voltage / current profile the charger advertises, with the active profile highlighted
- Charging diagnostics: identifies bottlenecks — cable limiting speed, charger undersized, etc.
- Live wattage (UCSI):
voltage_now × current_now, exposed asnegotiatedPowerMWin JSON output and viaTypeCPowerSupply::negotiated_power_mw()in the library. - Partner identity: decoded from PD Discover Identity VDOs
Install
From crates.io
cargo install usbeehive
Build from source
Requires Rust 1.85+ (rustup) and libudev development
headers (for --watch hotplug support, on by default).
# Ubuntu / Debian
sudo apt install libudev-dev pkg-config
# Fedora
sudo dnf install systemd-devel pkgconf-pkg-config
# Arch / Manjaro
sudo pacman -S --needed systemd-libs pkgconf
cargo build --release # default (with --watch)
cargo build --release --no-default-features --features cli,sysfs # no libudev
sudo install -Dm755 target/release/usbeehive /usr/local/bin/usbeehive
Tests
cargo test # full suite (requires libudev-dev)
cargo test --no-default-features # pure-decoder subset, no libudev
CLI usage
usbeehive # human-readable summary of every USB device
usbeehive --json # structured JSON output
usbeehive --watch # stream updates as devices come and go
usbeehive --raw # include raw sysfs attributes
usbeehive --sysfs-root /tmp/fixture-root # run against a captured tree (testing)
usbeehive --version
usbeehive --help
Library usage
The crate has three optional layers, each behind a Cargo feature so consumers pull in only what they need.
| Feature | Default | Adds |
|---|---|---|
| (none) | always | Data types, USB-PD VDO decoders, diagnostics, summaries — IO-free. |
sysfs |
yes | Sysfs handle + DeviceManager — Linux /sys enumeration with injectable root. |
watch |
yes | libudev hotplug monitor: watch::Watcher + watch::run_loop. |
cli |
yes | The usbeehive binary (clap + JSON / text rendering). |
dbus |
no | usbeehive::dbus interface module + the usbeehived daemon binary publishing org.usbeehive.Devices2 on the session bus (implies watch). |
Library-only consumers can drop the binary deps:
# Pure decoders, no IO, no libudev:
usbeehive = { version = "0.5", default-features = false }
# Add /sys enumeration:
usbeehive = { version = "0.5", default-features = false, features = ["sysfs"] }
# Add hotplug too:
usbeehive = { version = "0.5", default-features = false, features = ["watch"] }
Decode a Cable VDO
use usbeehive::pd::{decode_cable_vdo, CableSpeed, CableCurrent};
let v = decode_cable_vdo(2 | (2 << 5) | (3 << 9), false);
assert_eq!(v.speed, CableSpeed::Usb32Gen2);
assert_eq!(v.current_rating, CableCurrent::FiveAmp);
assert_eq!(v.max_watts, 250);
Enumerate the system's USB tree
use usbeehive::DeviceManager;
let mut mgr = DeviceManager::new();
mgr.refresh();
for s in mgr.devices() {
println!("{}: {}", s.headline, s.subtitle);
}
Run a debounced render loop on hotplug events
use std::time::Duration;
use usbeehive::{watch::run_loop, DeviceManager};
let mut mgr = DeviceManager::new();
run_loop(Duration::from_millis(500), |_reason| {
mgr.refresh();
println!("snapshot has {} devices", mgr.devices().len());
Ok(())
}).unwrap();
See examples/ for more.
D-Bus daemon (optional)
Build with the dbus feature to get a long-running daemon, usbeehived,
that publishes the live snapshot on the session bus. Useful for desktop
applets (KDE / GNOME / tray apps) that want to react to cable plugs and
charging-bottleneck changes without each one re-implementing sysfs
enumeration.
cargo build --release --no-default-features --features dbus
./target/release/usbeehived # foreground
Wire surface — org.usbeehive.Devices2 at /org/usbeehive/Devices:
Each ListDevices entry is a 19-field tuple:
(id, category, device_class, device_subclass, status, headline, subtitle, icon, vendor, product, vendor_id, product_id, primary_driver, properties, port_number, link_speed_mbps, usb_version, power, charging_diag). See src/dbus.rs module docs for per-field semantics.
| Member | Kind | Notes |
|---|---|---|
ListDevices |
method () → a(ssssssssssqqsa(ss)ius(uus)(bsssb)) |
One structured entry per summary. Properties are (machine_key, value) pairs. |
ListPorts |
method () → ai |
Type-C port_numbers currently exposed. |
Diagnose |
method (i) → (bsssb) |
Charging diagnostic for a port — same shape as the per-entry charging_diag. present == false when none available. |
SnapshotJson |
method () → s |
Full structured snapshot — same shape as usbeehive --json. |
Refresh |
method () → u |
Force a re-enumeration; returns the new device count. |
Version / DeviceCount |
properties | Crate version + summary count. |
DeviceAdded / DeviceRemoved |
signals | Fire when a device or port appears / disappears. |
CapabilityDegraded / CapabilityRestored |
signals | Fire when a port's charging diagnostic newly raises (or clears) is_warning — e.g. a too-thin cable plugged into a beefy charger. |
Quick poke from the shell:
gdbus call --session --dest org.usbeehive.Devices \
--object-path /org/usbeehive/Devices \
--method org.usbeehive.Devices2.ListDevices
A minimal Rust client lives in examples/dbus_client.rs.
Devices2 migration
Devices1 was retired in favor of Devices2 — clients must update both
the interface name and how they consume the per-entry payload. Adding
new enum variants (device_class, status, power_role, bottleneck)
is non-breaking; clients MUST treat any unrecognized string as
Unknown / fall back to category-based behavior. The CHANGELOG has the
full regex → field migration table.
How it works
usbeehive reads three areas of the Linux sysfs filesystem. No root access required for basic info:
| sysfs path | Provides |
|---|---|
/sys/bus/usb/devices/ |
All USB devices: vendor, product, speed, power, class, interfaces, topology |
/sys/class/typec/ |
USB-C port state: connection, roles, cable e-marker, partner identity |
/sys/class/usb_power_delivery/ |
PD negotiation: PDO list from charger, active profile, PPS ranges |
/sys/class/power_supply/ucsi-source-psy-* |
Live voltage_now × current_now charging readout |
Hotplug uses libudev to detect connect/disconnect events in real time.
Cable speed and power decoding follow the USB Power Delivery 3.x spec, ported from the original WhatCable's Swift implementation.
Caveats
- USB-C / PD data availability varies by hardware. The Type-C connector class and USB PD sysfs interfaces depend on the kernel driver (UCSI, TCPM, platform-specific). Some systems expose full PD negotiation data; others expose only basic port info or nothing at all.
- Cable e-marker info only appears for cables that carry one. Same as the original — most USB-C cables under 60W are unmarked.
- usbeehive trusts the e-marker. Counterfeit or mis-flashed cables can lie about their capabilities.
- Vendor name lookup is not exhaustive. Common vendors are recognized; others show the hex VID.
Credits
Original macOS app: WhatCable by Darryl Morley — and thanks to Darryl for suggesting the new name when the Rust port outgrew its borrowed one. The USB Power Delivery decoding logic, charging diagnostics, vendor database, and plain-English summary approach are derived from the original macOS app.
Upstream Linux/KDE C++ port: Zetaphor/whatcable-linux.
License
Dependencies
~0.2–7.5MB
~156K SLoC