# usbtree Cross-platform TUI for inspecting the USB device tree (Linux, macOS, Windows). Enumerates via [nusb](https://crates.io/crates/nusb) — pure Rust, no root, no libusb. One static binary, zero runtime deps: **~1.5 MB download, ~5–7 MB on disk**. ![usbtree](https://img.shields.io/badge/rust-ratatui-blue) [![license: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) [![ci](https://github.com/gnomeria/usbtree/actions/workflows/ci.yml/badge.svg)](https://github.com/gnomeria/usbtree/actions/workflows/ci.yml) [![release](https://img.shields.io/github/v/release/gnomeria/usbtree)](https://github.com/gnomeria/usbtree/releases/latest) **Website:** [gnomeria.github.io/usbtree](https://gnomeria.github.io/usbtree) ![usbtree demo](docs/screenshots/demo.gif) ## Platform support | Feature | Linux | macOS | Windows | | ---------------------------------------------------- | :-----------: | :---: | :-----: | | Device tree — hubs, classes, speeds, tree rails | ✅ | ✅ | ✅ | | Friendly names (`overrides.ids` + usb.ids) | ✅ | ✅ | ✅ | | Hot-plug watch + timestamped event log | ✅ | ✅ | ✅ | | Detail panel — sysfs path, vid:pid, serial, children | ✅ | ✅ | ✅ | | Device power — advertised `bMaxPower` | ✅ | ✅ | — | | Live activity sparklines — URBs/s (unprivileged) | ✅ | — | — | | Real bandwidth — bytes/s via usbmon (root) | ✅ | — | — | | Prebuilt binaries | amd64 · arm64 | arm64 | amd64 | Live per-device activity is Linux-only — [why →](#activity-metrics-linux). ## Install > [!NOTE] > The shell installer and prebuilt links need a published GitHub release. None yet? Install from source. **Homebrew** (Linux · macOS Apple Silicon) ```sh brew install gnomeria/tap/usbtree ``` **Linux / macOS** (shell installer) ```sh curl -fsSL https://raw.githubusercontent.com/gnomeria/usbtree/main/scripts/install.sh | sh ``` **Windows** ```powershell irm https://raw.githubusercontent.com/gnomeria/usbtree/main/scripts/install.ps1 | iex ``` **From source** ```sh cargo install --git https://github.com/gnomeria/usbtree ``` Installers verify the archive's sha256 against `checksums.txt` and install to `/usr/local/bin` or `~/.local/bin` (Linux/macOS) / `%LOCALAPPDATA%\usbtree\bin` + user `PATH` (Windows). Env vars (prefix `$env:` in PowerShell): | Variable | Effect | | ---------------------- | ---------------------------------------------------------- | | `USBTREE_VERSION` | pin a version, e.g. `0.0.1` (default: latest) | | `USBTREE_INSTALL_DIR` | install directory override | | `USBTREE_SUDO_SYMLINK` | `1` also symlinks into `/usr/local/bin` via `sudo`, so `sudo usbtree` finds it for usbmon bytes/s | Or grab a `usbtree__-.{tar.gz,zip}` archive from the [latest release](https://github.com/gnomeria/usbtree/releases/latest); each sha256 is in `checksums.txt`. > [!NOTE] > **macOS and Windows binaries are not code-signed or notarized.** > - **macOS**: the install script and Homebrew formula clear the quarantine flag; manual downloads need `xattr -d com.apple.quarantine ./usbtree` (or right-click → Open once). > - **Windows**: SmartScreen may warn — _More info_ → _Run anyway_, or `Unblock-File usbtree.exe`. > Verify the sha256 or build from source if in doubt. ## Features - Live USB tree — color-coded class gutter, per-class icons, tree rails, speed badges (`▂` low/full, `▄` high 480M, `█` SuperSpeed+ 5G/10G); rescans every second. Collapse hubs with `Enter`/`Space`/`h`/`l` → `▸` + `+N` child badge - Names via fallback chain: `overrides.ids` → descriptor strings → downloaded [usb.ids](http://www.linux-usb.org/usb-ids.html) (`--updatelist`) → embedded snapshot → vendor/class heuristics - Composite/Misc (0xef) devices classified by interface class, so e.g. a MOTU M2 shows as Audio, not Misc - Hot-plug watch — plugged flash green, unplugged linger as red ghosts for 30 s, all events logged with timestamps - Live per-device activity (Linux) — inline sparklines + detail-pane bandwidth graph; URBs/s unprivileged, real bytes/s via usbmon (root, see below) - Detail panel — sysfs path, vid:pid, vendor, class, speed, `bMaxPower`, serial, connected children - Safe eject (Linux, unprivileged) — `e` on a mass-storage device unmounts + cuts port power via udisks2, with a confirm dialog - PCI view (`p`) — flat address-sorted PCI list with detail pane (prog-if, subsystem, link speed/width, NUMA, IOMMU group, power state) - Live filter (`/`), yank (`y` vid:pid, `Y` full details), `--dump` prints the tree once (no TUI) ## Usage ```sh usbtree # TUI usbtree --dump # print the tree once and exit usbtree --updatelist # download the latest usb.ids into the config dir usbtree --upgrade # update to the latest release (re-runs the installer) usbtree --version # print the version usbtree --help # list all flags (also -h) usbtree --demo # fake tree with scripted hot-plug + traffic (no hardware) usbtree --light # use light theme (for light background terminals) ``` `--upgrade` re-runs the official installer for your platform, so it honours the same env vars — e.g. `USBTREE_VERSION=0.1.0 usbtree --upgrade` pins a version, `USBTREE_INSTALL_DIR` picks the target dir. Homebrew installs: use `brew upgrade usbtree` instead. | Key | Action | | --------------------- | ------------------- | | `j`/`k`, arrows | move selection | | `Enter`/`Space` | collapse/expand hub | | `h`/`←`, `l`/`→` | fold / unfold | | `g`/`Home`, `G`/`End` | top / bottom | | `/` | filter tree | | `Tab` | focus tree / events | | `y` / `Y` | yank id / details | | `e` | safe-eject storage | | `p` | toggle USB / PCI | | `r` | force rescan | | `t` | toggle theme | | `q` / `Esc` | quit | ## Configuration Config lives in `~/.config/usbtree/` (`%APPDATA%\usbtree\` on Windows): - **`overrides.ids`** — personal names, one `vvvv:pppp Friendly Name` per line (`#` comments OK). Wins over descriptor strings and usb.ids: ``` 07fd:000b MOTU M2 Audio Interface ``` - **`usb.ids`** — written by `usbtree --updatelist` (from the [systemd/hwdata](https://github.com/systemd/hwdata) mirror). Takes priority over the snapshot compiled into the binary. ## Activity metrics (Linux) The header shows the active source: - **`◌ urb activity`** — unprivileged default: URB-count deltas from sysfs `urbnum`, shown as URBs/s - **`◉ usbmon bytes/s`** — real per-device bandwidth when `/sys/kernel/debug/usb/usbmon` is readable (root + usbmon module loaded) `sudo` alone is **not enough** — the `usbmon` module must be loaded or usbtree silently falls back to URB activity: ```sh sudo modprobe usbmon sudo "$(command -v usbtree)" ``` The absolute path matters: `sudo`'s `secure_path` excludes `~/.local/bin`, so plain `sudo usbtree` fails unless installed to `/usr/local/bin`. Load usbmon at boot with `echo usbmon | sudo tee /etc/modules-load.d/usbmon.conf`; needs `CONFIG_USB_MON` (standard on mainstream kernels). macOS/Windows have no unprivileged per-device traffic counter (`sudo` doesn't help), so live activity isn't implemented there — the header reads `◌ activity n/a on this platform`. ## How it works Rescans every second; hot-plug detection is a snapshot diff between scans. Paths use sysfs-style naming (`1-1.4` = bus 1, port 1, port 4) on every platform, built from each device's port chain; root hubs are synthesized from the bus list. Releases are automated with [release-please](https://github.com/googleapis/release-please): conventional commits on `main` roll into a release PR; merging it tags a version and builds the binaries. ## Development Common commands live in the [Taskfile](https://taskfile.dev) — `task -l` lists them (`task demo`, `task test`, `task lint`, `task ci`, `task shots`, `task hooks`). The demo GIF/PNG in `docs/screenshots/` are rendered headlessly by driving `usbtree --demo` with [VHS](https://github.com/charmbracelet/vhs) tapes from `tapes/`, re-committed by the [Screenshots workflow](.github/workflows/screenshots.yml) when `src/` or the tapes change. Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md); security reports follow [SECURITY.md](SECURITY.md). ## License [MIT](LICENSE).