MCPcopy Index your code
hub / github.com/abrauchli/usbeehive

github.com/abrauchli/usbeehive @v0.11.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.11.0 ↗ · + Follow
482 symbols 1,233 edges 35 files 119 documented · 25%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

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 as whatcable — at the original author's request, it has been renamed to usbeehive as of 0.5.0. The old crate is retired (its final release is a redirect to here). Use cargo install usbeehive going 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 as negotiatedPowerMW in JSON output and via TypeCPowerSupply::negotiated_power_mw() in the library.
  • Partner identity: decoded from PD Discover Identity VDOs

Install

From crates.io

cargo install usbeehive

To run the usbee GNOME Shell extension, install with the D-Bus daemon enabled:

cargo install usbeehive --features dbus

--features dbus builds the usbeehived daemon, which compiles C code and needs a C compiler (cc) plus the libudev development headers:

# Ubuntu / Debian
sudo apt install gcc libudev-dev

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.Devices5 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

```rust,no_run 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

```rust,no_run
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. The usbee GNOME Shell extension is one such client.

cargo build --release --no-default-features --features dbus
./target/release/usbeehived                                # foreground

Building the dbus feature requires a C compiler (cc) and the libudev development headers — on Ubuntu / Debian: sudo apt install gcc libudev-dev.

Wire surface — org.usbeehive.Devices5 at /org/usbeehive/Devices:

Each ListDevices entry is a 21-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, pdo_list, active_pdo_index). See src/dbus.rs module docs for per-field semantics.

Member Kind Notes
ListDevices method () → a(ssssssssssqqsa(ss)ius(uuus)(bsssb)a(usuuuub)i) One structured entry per summary. Properties are (machine_key, value) pairs; pdo_list is the structured source-PDO advertisement.
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.Devices5.ListDevices

A minimal Rust client lives in examples/dbus_client.rs.

Devices5 migration

Devices4 was retired in favor of Devices5, continuing the honest-units theme: USB-PD wattages on the wire are negotiated ceilings, never measured flow — UCSI reports the contract's operating point, and a sink that is e.g. battery-charge-limited may request far less than its contract allows. The break:

  • The per-entry power tuple grows a field: (uus)(uuus), now (power_in_mw, power_out_mw, contract_mw, power_role). power_in_mw remains the sink's requested operating power ("up to N W"); the new contract_mw is what the active PDO contract allows (0 = no contract inferred). contract_mw > power_in_mw means the sink is limiting its own draw, not the cable or charger.
  • New bottleneck variant SinkLimit (always is_warning == false): contract healthy, sink requesting < 80% of it — typically a battery charge limit or thermal policy. Falls under the existing unknown-variant rule for older clients.

Update the interface name and any positional unpacking of the power tuple (power_role moves from index 2 to 3); every method/signal is otherwise unchanged. 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. Machine-keyed flag properties (cable.trust.*, transport.*) are pushed only when their flag fires — absence is the "off" state. The CHANGELOG has the full break-by-break 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

MIT

Core symbols most depended-on inside this repo

path
called by 64
tests/fixture_builder.rs
is_empty
called by 57
src/sysfs/manager.rs
write_attr
called by 33
tests/fixture_builder.rs
read_attr
called by 28
src/sysfs/reader.rs
refresh
called by 24
src/dbus.rs
build_summaries
called by 24
src/sysfs/manager.rs
path
called by 19
src/sysfs/reader.rs
write
called by 17
src/sysfs/reader.rs

Shape

Function 350
Method 81
Class 38
Enum 13

Languages

Rust100%

Modules by API surface

src/summary.rs74 symbols
src/sysfs/manager.rs36 symbols
src/pd.rs32 symbols
src/sysfs/reader.rs31 symbols
src/output.rs31 symbols
src/dbus.rs24 symbols
src/sysfs/typec.rs23 symbols
src/power.rs21 symbols
src/diagnostic.rs21 symbols
src/usb.rs19 symbols
tests/fixture_builder.rs18 symbols
src/typec.rs18 symbols

For agents

$ claude mcp add usbeehive \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact