Tutto è partito da qui https://kau.sh/blog/usbi/.
Visto che lui lo ha creato per apple e che anche io ho un mare di cavi, ma nel mio personalissimo generatore di entropia, ho varie difficolta a ricordare quali sono i cavi buoni e quelli farlocchi.
Di conseguenza se lo ha fatto lui, mi son detto, magari riesco a farlo anche io.
metto qui di seguito prima info1.txt poi usbi.go e dopo di nuovo info2.txt ed usbi.go che sarà la versione 2 quella con le scorciatoie
#info1.txt
# Installa le dipendenze necessarie
sudo apt-get install usbutils golang-go
# Compila il programma
go build usbi.go
# Esegui (alcune info potrebbero richiedere sudo)
./usbi
sudo ./usbi # Per informazioni più dettagliate
# Esempi di utilizzo
./usbi --speed # Focus sulla velocità
./usbi --power # Focus sul consumo
./usbi --problems # Solo dispositivi con problemi
./usbi --summary # Statistiche riassuntive
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
)
// Core data structures
type USBDevice struct {
Name string
Level int
Speed string
Manufacturer string
VendorID string
ProductID string
PowerRequired int
PowerAvailable int
LocationID string
BusNumber string
DeviceNumber string
IsBus bool
BusDriver string
}
type DisplayOptions struct {
Mode string
UseColor bool
UseASCII bool
}
type Summary struct {
DeviceCount int
HubCount int
TotalPowerUsed int
TotalPowerAvail int
ProblemCount int
SpeedCategories map[string]int
}
type DeviceContent struct {
Name string
Category string
HealthIndicators string
Speed string
SpeedCategory string
TransferRate string
Manufacturer string
VendorID string
PowerText string
PowerUsage float64
Location string
HasProblems bool
HasSpeedProblems bool
HasPowerProblems bool
IsHub bool
IsBus bool
BusDriver string
Level int
TreePrefix string
AttributeIndent string
}
// Color functions
func colorize(text, color string, useColor bool) string {
if !useColor {
return text
}
colors := map[string]string{
"red": "\033[31m",
"green": "\033[32m",
"yellow": "\033[33m",
"blue": "\033[34m",
"magenta": "\033[35m",
"cyan": "\033[36m",
"white": "\033[37m",
"bold": "\033[1m",
"dim": "\033[2m",
"reset": "\033[0m",
}
if code, exists := colors[color]; exists {
return code + text + colors["reset"]
}
return text
}
func red(text string, useColor bool) string { return colorize(text, "red", useColor) }
func green(text string, useColor bool) string { return colorize(text, "green", useColor) }
func yellow(text string, useColor bool) string { return colorize(text, "yellow", useColor) }
func blue(text string, useColor bool) string { return colorize(text, "blue", useColor) }
func cyan(text string, useColor bool) string { return colorize(text, "cyan", useColor) }
func white(text string, useColor bool) string { return colorize(text, "white", useColor) }
func bold(text string, useColor bool) string { return colorize(text, "bold", useColor) }
func dim(text string, useColor bool) string { return colorize(text, "dim", useColor) }
// Vendor database
func getVendorName(vendorID, originalName string) string {
vendorMap := map[string]string{
"18d1": "Google",
"05ac": "Apple",
"1532": "Razer",
"046d": "Logitech",
"8087": "Intel",
"2188": "CalDigit",
"1a40": "Terminus Tech",
"1a86": "QinHeng Electronics",
"0781": "SanDisk",
"0930": "Toshiba",
"152d": "JMicron",
"174c": "ASMedia",
"1058": "Western Digital",
"04e8": "Samsung",
"0bc2": "Seagate",
"1d6b": "Linux Foundation",
}
cleanID := strings.ToLower(strings.TrimPrefix(vendorID, "0x"))
if vendor, exists := vendorMap[cleanID]; exists {
return vendor
}
return originalName
}
// Device to USB standard mapping
func getDeviceMaxUSBStandard(deviceName, vendorID string) string {
nameLower := strings.ToLower(deviceName)
pixelDevices := map[string]string{
"pixel 9 pro": "USB 3.2",
"pixel 9": "USB 3.2",
"pixel 8 pro": "USB 3.2",
"pixel 8": "USB 3.0",
"pixel 7 pro": "USB 3.1",
"pixel 7": "USB 3.0",
"pixel 6 pro": "USB 3.1",
"pixel 6": "USB 3.0",
"pixel 5": "USB 3.0",
"pixel 4": "USB 3.0",
"pixel 3": "USB 2.0",
"pixel 2": "USB 2.0",
}
for deviceKey, usbStandard := range pixelDevices {
if strings.Contains(nameLower, deviceKey) {
return usbStandard
}
}
if strings.Contains(nameLower, "ssd") || strings.Contains(nameLower, "nvme") {
return "USB 3.1"
}
if strings.Contains(nameLower, "hdd") || strings.Contains(nameLower, "drive") {
return "USB 3.0"
}
if strings.Contains(nameLower, "mouse") || strings.Contains(nameLower, "keyboard") {
return "USB 2.0"
}
return "USB 2.0"
}
func getUSBStandardMaxSpeed(standard string) string {
speedMap := map[string]string{
"USB 1.0": "1.5 Mb/s",
"USB 1.1": "12 Mb/s",
"USB 2.0": "480 Mb/s",
"USB 3.0": "5 Gb/s",
"USB 3.1": "10 Gb/s",
"USB 3.2": "20 Gb/s",
"USB4": "40 Gb/s",
}
if speed, exists := speedMap[standard]; exists {
return speed
}
return "Unknown"
}
func isSpeedSuboptimal(deviceName, vendorID, currentSpeed string) bool {
maxStandard := getDeviceMaxUSBStandard(deviceName, vendorID)
currentStandard := categorizeSpeed(currentSpeed)
if maxStandard == "USB 2.0" || currentStandard == "" {
return false
}
standardOrder := map[string]int{
"USB 1.0": 1,
"USB 1.1": 2,
"USB 2.0": 3,
"USB 3.0": 4,
"USB 3.1": 5,
"USB 3.2": 6,
"USB4": 7,
}
maxOrder, maxExists := standardOrder[maxStandard]
currentOrder, currentExists := standardOrder[currentStandard]
if !maxExists || !currentExists {
return false
}
return currentOrder < (maxOrder - 1)
}
// Device categorization
func categorizeDevice(name, vendorID, productID string) string {
nameLower := strings.ToLower(name)
categories := map[string][]string{
"Hub": {"hub", "root hub"},
"Mouse/Trackpad": {"mouse", "trackpad", "touchpad"},
"Keyboard": {"keyboard"},
"Phone": {"phone", "pixel", "iphone", "android"},
"Storage": {"drive", "disk", "storage", "ssd", "hdd", "flash", "card reader"},
"Camera": {"camera", "webcam"},
"Audio": {"audio", "speaker", "headphone", "microphone", "sound"},
"Printer": {"printer"},
"Adapter": {"adapter", "dongle", "bluetooth"},
"Composite Device": {"composite"},
}
for category, keywords := range categories {
for _, keyword := range keywords {
if strings.Contains(nameLower, keyword) {
return category
}
}
}
return "Unknown Device"
}
// Speed categorization
func categorizeSpeed(speed string) string {
speedLower := strings.ToLower(speed)
if strings.Contains(speedLower, "1.5m") {
return "USB 1.0"
}
if strings.Contains(speedLower, "12m") {
return "USB 1.1"
}
if strings.Contains(speedLower, "480m") {
return "USB 2.0"
}
if strings.Contains(speedLower, "5000m") || strings.Contains(speedLower, "5g") {
return "USB 3.0"
}
if strings.Contains(speedLower, "10000m") || strings.Contains(speedLower, "10g") {
return "USB 3.1"
}
if strings.Contains(speedLower, "20000m") || strings.Contains(speedLower, "20g") {
return "USB 3.2"
}
if strings.Contains(speedLower, "40000m") || strings.Contains(speedLower, "40g") {
return "USB4"
}
return ""
}
func getTransferRate(speedCategory string) string {
rates := map[string]string{
"USB 1.0": "~0.2 MB/s max",
"USB 1.1": "~1.5 MB/s max",
"USB 2.0": "~60 MB/s max",
"USB 3.0": "~625 MB/s max",
"USB 3.1": "~1.25 GB/s max",
"USB 3.2": "~2.5 GB/s max",
"USB4": "~5 GB/s max",
}
return rates[speedCategory]
}
// Problem detection
func hasProblems(device USBDevice) bool {
if device.Name == "" || device.Speed == "" {
return true
}
if device.PowerRequired > 0 && device.PowerAvailable > 0 {
if device.PowerRequired > device.PowerAvailable {
return true
}
usage := float64(device.PowerRequired) / float64(device.PowerAvailable)
if usage > 0.95 {
return true
}
}
if isSpeedSuboptimal(device.Name, device.VendorID, device.Speed) {
return true
}
return false
}
func hasNonPowerProblems(device USBDevice) bool {
if device.Name == "" || device.Speed == "" {
return true
}
if device.PowerRequired > 0 && device.PowerAvailable > 0 {
if device.PowerRequired > device.PowerAvailable {
return true
}
}
if isSpeedSuboptimal(device.Name, device.VendorID, device.Speed) {
return true
}
return false
}
func hasPowerProblems(device USBDevice) bool {
if device.PowerRequired > 0 && device.PowerAvailable > 0 {
if device.PowerRequired > device.PowerAvailable {
return true
}
usage := float64(device.PowerRequired) / float64(device.PowerAvailable)
if usage > 0.95 {
return true
}
}
return false
}
func hasSpeedProblems(device USBDevice) bool {
return isSpeedSuboptimal(device.Name, device.VendorID, device.Speed)
}
func isHub(name string) bool {
nameLower := strings.ToLower(name)
return strings.Contains(nameLower, "hub") || strings.Contains(nameLower, "root hub")
}
// Tree rendering helpers
func getTreePrefix(level int, isLast bool, useASCII bool, treeContinues []bool) string {
if level == 0 {
return ""
}
var vert, branch, lastBranch string
if useASCII {
vert = "|"
branch = "+-"
lastBranch = "\\-"
} else {
vert = "│"
branch = "├─"
lastBranch = "└─"
}
prefix := ""
for i := 1; i < level; i++ {
if i < len(treeContinues) && treeContinues[i] {
prefix += vert + " "
} else {
prefix += " "
}
}
if isLast {
prefix += lastBranch + " "
if level < len(treeContinues) {
treeContinues[level] = false
}
} else {
prefix += branch + " "
if level < len(treeContinues) {
treeContinues[level] = true
}
}
return prefix
}
func getAttributeIndent(displayLevel int, isLast bool, useASCII bool, treeContinues []bool) string {
indent := ""
vert := "│"
if useASCII {
vert = "|"
}
for i := 1; i <= displayLevel; i++ {
if i < displayLevel && i < len(treeContinues) && treeContinues[i] {
indent += vert + " "
} else if i < displayLevel {
indent += " "
} else if !isLast {
indent += vert + " "
} else {
indent += " "
}
}
return indent
}
func isLastAtLevel(devices []USBDevice, currentIndex, level int, isBus bool) bool {
for i := currentIndex + 1; i < len(devices); i++ {
if devices[i].IsBus && !isBus {
break
}
if isBus {
if devices[i].IsBus {
return false
}
} else {
if devices[i].Level <= level {
if devices[i].Level == level {
return false
}
break
}
}
}
return true
}
// Parse lsusb output for Linux
func runSystemCommand() ([]USBDevice, error) {
// Check if lsusb is available
if _, err := exec.LookPath("lsusb"); err != nil {
return nil, fmt.Errorf("lsusb not found. Please install usbutils: sudo apt-get install usbutils")
}
// Get basic device list
cmd := exec.Command("lsusb")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to run lsusb: %v", err)
}
var devices []USBDevice
busMap := make(map[string]bool)
// Parse lsusb output
scanner := bufio.NewScanner(strings.NewReader(string(output)))
for scanner.Scan() {
line := scanner.Text()
// Format: Bus 001 Device 002: ID 8087:8000 Intel Corp.
re := regexp.MustCompile(`Bus (\d+) Device (\d+): ID ([0-9a-fA-F]{4}):([0-9a-fA-F]{4})(.*)`)
matches := re.FindStringSubmatch(line)
if len(matches) < 6 {
continue
}
busNum := matches[1]
devNum := matches[2]
vendorID := matches[3]
productID := matches[4]
description := strings.TrimSpace(matches[5])
// Add bus if not seen before
if !busMap[busNum] {
busMap[busNum] = true
devices = append(devices, USBDevice{
Name: fmt.Sprintf("USB Bus %s", busNum),
Level: 0,
IsBus: true,
BusNumber: busNum,
BusDriver: "Linux USB",
})
}
// Get detailed info for this device
detailCmd := exec.Command("lsusb", "-v", "-s", fmt.Sprintf("%s:%s", busNum, devNum))
detailOutput, err := detailCmd.Output()
var speed, manufacturer string
var powerRequired, powerAvailable int
if err == nil {
detailScanner := bufio.NewScanner(strings.NewReader(string(detailOutput)))
for detailScanner.Scan() {
detailLine := strings.TrimSpace(detailScanner.Text())
// Speed
if strings.Contains(detailLine, "bcdUSB") {
if strings.Contains(detailLine, "1.00") {
speed = "1.5 Mb/s"
} else if strings.Contains(detailLine, "1.10") {
speed = "12 Mb/s"
} else if strings.Contains(detailLine, "2.00") {
speed = "480 Mb/s"
} else if strings.Contains(detailLine, "3.00") {
speed = "5000 Mb/s"
} else if strings.Contains(detailLine, "3.10") {
speed = "10000 Mb/s"
}
}
// Manufacturer
if strings.HasPrefix(detailLine, "iManufacturer") {
parts := strings.SplitN(detailLine, " ", 3)
if len(parts) >= 3 {
manufacturer = strings.TrimSpace(parts[2])
}
}
// Power
if strings.Contains(detailLine, "MaxPower") {
re := regexp.MustCompile(`(\d+)mA`)
if m := re.FindStringSubmatch(detailLine); len(m) > 1 {
if val, err := strconv.Atoi(m[1]); err == nil {
powerRequired = val
powerAvailable = 500 // Default USB 2.0
if strings.Contains(speed, "5000") {
powerAvailable = 900 // USB 3.0
}
}
}
}
}
}
// Clean up description
if description == "" {
description = "Unknown Device"
}
// Use vendor name if available
if manufacturer != "" {
description = manufacturer + " " + description
} else {
vendorName := getVendorName(vendorID, "")
if vendorName != "" {
description = vendorName + " " + description
}
}
device := USBDevice{
Name: description,
Level: 1,
Speed: speed,
Manufacturer: manufacturer,
VendorID: vendorID,
ProductID: productID,
PowerRequired: powerRequired,
PowerAvailable: powerAvailable,
LocationID: fmt.Sprintf("Bus %s, Device %s", busNum, devNum),
BusNumber: busNum,
DeviceNumber: devNum,
IsBus: false,
}
devices = append(devices, device)
}
return devices, nil
}
// Generate unified content for all devices
func generateDeviceContent(devices []USBDevice, opts DisplayOptions) []DeviceContent {
var content []DeviceContent
treeContinues := make([]bool, 20)
for i, device := range devices {
if device.Name == "" {
continue
}
isLast := isLastAtLevel(devices, i, device.Level, device.IsBus)
if device.IsBus {
content = append(content, generateBusContent(device, opts))
} else {
content = append(content, generateSingleDeviceContent(device, isLast, opts, treeContinues))
}
}
return content
}
func generateBusContent(device USBDevice, opts DisplayOptions) DeviceContent {
return DeviceContent{
Name: device.Name,
BusDriver: device.BusDriver,
IsBus: true,
Level: device.Level,
}
}
func generateSingleDeviceContent(device USBDevice, isLast bool, opts DisplayOptions, treeContinues []bool) DeviceContent {
prefix := getTreePrefix(device.Level, isLast, opts.UseASCII, treeContinues)
attrIndent := getAttributeIndent(device.Level, isLast, opts.UseASCII, treeContinues)
category := ""
healthIndicators := ""
if !isHub(device.Name) {
category = categorizeDevice(device.Name, device.VendorID, device.ProductID)
speedCat := categorizeSpeed(device.Speed)
if speedCat == "USB 1.0" || speedCat == "USB 1.1" || speedCat == "USB 2.0" {
healthIndicators += " *"
}
if hasNonPowerProblems(device) && !hasSpeedProblems(device) && !hasPowerProblems(device) {
healthIndicators += " [Problem]"
}
}
var powerText string
var powerUsage float64
if device.PowerRequired > 0 && device.PowerAvailable > 0 {
powerUsage = float64(device.PowerRequired) / float64(device.PowerAvailable) * 100
powerText = fmt.Sprintf("Power: %dmA/%dmA [%.1f%%]", device.PowerRequired, device.PowerAvailable, powerUsage)
}
speedCat := categorizeSpeed(device.Speed)
speedText := ""
if device.Speed != "" {
speedText = fmt.Sprintf("Speed: %s", device.Speed)
if speedCat != "" {
speedText += fmt.Sprintf(" [%s]", speedCat)
}
maxStandard := getDeviceMaxUSBStandard(device.Name, device.VendorID)
shouldShowMax := false
nameLower := strings.ToLower(device.Name)
if strings.Contains(nameLower, "pixel") || strings.Contains(nameLower, "ssd") || strings.Contains(nameLower, "nvme") {
shouldShowMax = true
}
if maxStandard != "USB 2.0" && isSpeedSuboptimal(device.Name, device.VendorID, device.Speed) {
shouldShowMax = true
}
if shouldShowMax {
maxSpeed := getUSBStandardMaxSpeed(maxStandard)
if maxSpeed != "Unknown" {
speedText += fmt.Sprintf(" (max: %s)", maxSpeed)
}
}
}
transferRate := getTransferRate(speedCat)
return DeviceContent{
Name: device.Name,
Category: category,
HealthIndicators: healthIndicators,
Speed: speedText,
SpeedCategory: speedCat,
TransferRate: transferRate,
Manufacturer: device.Manufacturer,
VendorID: device.VendorID,
PowerText: powerText,
PowerUsage: powerUsage,
Location: device.LocationID,
HasProblems: hasProblems(device),
HasSpeedProblems: hasSpeedProblems(device),
HasPowerProblems: hasPowerProblems(device),
IsHub: isHub(device.Name),
IsBus: false,
Level: device.Level,
TreePrefix: prefix,
AttributeIndent: attrIndent,
}
}
// Mode-specific color application
func applyModeColors(content DeviceContent, mode string, useColor bool) {
if content.IsBus {
renderBusWithColors(content, mode, useColor)
return
}
displayName := content.Name
if content.IsHub {
displayName = bold(content.Name, useColor)
} else if content.Category != "" && !strings.Contains(content.Category, "Unknown") {
displayName = content.Category + " " + bold(content.Name, useColor)
} else {
displayName = bold(content.Name, useColor)
}
if content.HasProblems && strings.Contains(content.HealthIndicators, "[Problem]") {
healthPart := strings.Replace(content.HealthIndicators, "[Problem]", red("[Problem]", useColor), 1)
displayName = white(displayName, useColor) + healthPart
} else {
displayName = white(displayName+content.HealthIndicators, useColor)
}
fmt.Println(content.TreePrefix + displayName)
renderAttributesWithColors(content, mode, useColor)
}
func renderBusWithColors(content DeviceContent, mode string, useColor bool) {
busName := content.Name
if content.BusDriver != "" {
busName += " [" + content.BusDriver + "]"
}
fmt.Println(white(busName, useColor))
}
func renderAttributesWithColors(content DeviceContent, mode string, useColor bool) {
indent := content.AttributeIndent
if content.Speed != "" {
switch mode {
case "speed":
line := content.Speed
if content.TransferRate != "" {
line += " " + dim(content.TransferRate, useColor)
}
switch content.SpeedCategory {
case "USB 1.0", "USB 1.1":
line = red(line, useColor)
case "USB 2.0":
line = yellow(line, useColor)
case "USB 3.0", "USB 3.1":
line = green(line, useColor)
case "USB 3.2", "USB4":
line = cyan(line, useColor)
default:
line = dim(line, useColor)
}
fmt.Printf("%s%s\n", indent, line)
case "default":
line := content.Speed
if content.TransferRate != "" {
line += " " + content.TransferRate
}
if content.HasSpeedProblems {
fmt.Printf("%s%s\n", indent, red(line, useColor))
} else {
fmt.Printf("%s%s\n", indent, dim(line, useColor))
}
default:
line := content.Speed
if content.TransferRate != "" {
line += " " + content.TransferRate
}
fmt.Printf("%s%s\n", indent, dim(line, useColor))
}
}
if content.Manufacturer != "" {
fmt.Printf("%s%s %s\n", indent, dim("Manufacturer:", useColor), dim(content.Manufacturer, useColor))
}
if content.VendorID != "" {
fmt.Printf("%s%s %s\n", indent, dim("Vendor ID:", useColor), dim(content.VendorID, useColor))
}
if content.PowerText != "" {
switch mode {
case "power":
mainPart := fmt.Sprintf("Power: %dmA/%dmA",
extractPowerRequired(content.PowerText),
extractPowerAvailable(content.PowerText))
percentPart := fmt.Sprintf(" [%.1f%%]", content.PowerUsage)
var line string
if content.PowerUsage > 90 {
line = red(mainPart, useColor) + dim(percentPart, useColor)
} else if content.PowerUsage > 50 {
line = yellow(mainPart, useColor) + dim(percentPart, useColor)
} else {
line = green(mainPart, useColor) + dim(percentPart, useColor)
}
fmt.Printf("%s%s\n", indent, line)
case "default":
if content.HasPowerProblems {
fmt.Printf("%s%s\n", indent, red(content.PowerText, useColor))
} else {
fmt.Printf("%s%s\n", indent, dim(content.PowerText, useColor))
}
default:
fmt.Printf("%s%s\n", indent, dim(content.PowerText, useColor))
}
}
if content.Location != "" {
switch mode {
case "location":
fmt.Printf("%s%s %s\n", indent, dim("Location:", useColor), white(content.Location, useColor))
default:
fmt.Printf("%s%s %s\n", indent, dim("Location:", useColor), dim(content.Location, useColor))
}
}
}
func extractPowerRequired(powerText string) int {
re := regexp.MustCompile(`(\d+)mA/`)
matches := re.FindStringSubmatch(powerText)
if len(matches) > 1 {
if val, err := strconv.Atoi(matches[1]); err == nil {
return val
}
}
return 0
}
func extractPowerAvailable(powerText string) int {
re := regexp.MustCompile(`/(\d+)mA`)
matches := re.FindStringSubmatch(powerText)
if len(matches) > 1 {
if val, err := strconv.Atoi(matches[1]); err == nil {
return val
}
}
return 0
}
func calculateSummary(devices []USBDevice) Summary {
summary := Summary{
SpeedCategories: make(map[string]int),
}
for _, device := range devices {
if device.IsBus {
continue
}
summary.DeviceCount++
if isHub(device.Name) {
summary.HubCount++
}
if device.PowerRequired > 0 {
summary.TotalPowerUsed += device.PowerRequired
}
if device.PowerAvailable > 0 {
summary.TotalPowerAvail += device.PowerAvailable
}
if device.Speed != "" {
speedCat := categorizeSpeed(device.Speed)
if speedCat != "" {
summary.SpeedCategories[speedCat]++
}
}
if hasProblems(device) {
summary.ProblemCount++
}
}
return summary
}
func renderSummary(summary Summary, mode string, useColor bool) {
fmt.Println()
switch mode {
case "summary":
fmt.Printf("Total Devices: %d (excluding hubs)\n", summary.DeviceCount-summary.HubCount)
fmt.Printf("Hubs: %d\n", summary.HubCount)
fmt.Println()
if summary.ProblemCount == 0 {
fmt.Println(green("No USB problems detected!", useColor))
} else {
fmt.Printf("%s %d device(s) with problems\n", red("Found", useColor), summary.ProblemCount)
}
case "power":
renderPowerLegend(useColor)
case "speed":
renderSpeedLegend(useColor)
}
}
func renderPowerLegend(useColor bool) {
fmt.Println(blue("Power Legend:", useColor))
fmt.Printf(" %s - Low usage, efficient\n", green("< 50%", useColor))
fmt.Printf(" %s - Moderate usage, monitor\n", yellow("> 50%", useColor))
fmt.Printf(" %s - High usage, may cause issues\n", red("> 90%", useColor))
}
func renderSpeedLegend(useColor bool) {
fmt.Println(blue("Speed Legend:", useColor))
fmt.Printf(" %s - Very slow, legacy devices\n", red("USB 1.0/1.1", useColor))
fmt.Printf(" %s - Slower, older devices\n", yellow("USB 2.0", useColor))
fmt.Printf(" %s - Fast, modern devices\n", green("USB 3.0/3.1", useColor))
fmt.Printf(" %s - Very fast, latest devices\n", cyan("USB 3.2/USB4", useColor))
}
func printHelp() {
fmt.Println(`Usage: usbi [MODE]
Modes:
default: Enhanced info with categories, vendor names, and health indicators
--raw: Raw lsusb output
--speed: Speed-focused tree with actual values [category speed]
--power: Power-focused tree with usage warnings [req/avail mA]
--problems: Show only devices with issues (power, speed, driver problems)
--location: Show location info along with manufacturer details
--summary: Port utilization, power totals, device counts
--no-color: Disable colored output
Color thresholds (power): red >90%, yellow >50%, green otherwise
Requirements:
- lsusb (install with: sudo apt-get install usbutils)
- May require sudo for detailed device information`)
}
func main() {
opts := DisplayOptions{
Mode: "default",
UseColor: true,
UseASCII: false,
}
for _, arg := range os.Args[1:] {
switch arg {
case "--raw":
opts.Mode = "raw"
case "--speed":
opts.Mode = "speed"
case "--power":
opts.Mode = "power"
case "--problems":
opts.Mode = "problems"
case "--location":
opts.Mode = "location"
case "--summary":
opts.Mode = "summary"
case "--no-color":
opts.UseColor = false
case "--ascii":
opts.UseASCII = true
case "--help", "-h":
printHelp()
return
}
}
if opts.UseColor {
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
opts.UseColor = false
}
}
if opts.Mode == "raw" {
cmd := exec.Command("lsusb")
output, err := cmd.Output()
if err != nil {
fmt.Fprintf(os.Stderr, "Error running lsusb: %v\n", err)
os.Exit(1)
}
fmt.Print(string(output))
return
}
devices, err := runSystemCommand()
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing USB data: %v\n", err)
os.Exit(1)
}
if opts.Mode == "problems" {
var filteredDevices []USBDevice
for _, device := range devices {
if device.IsBus || hasProblems(device) {
filteredDevices = append(filteredDevices, device)
}
}
devices = filteredDevices
}
content := generateDeviceContent(devices, opts)
summary := calculateSummary(devices)
fmt.Println()
for _, deviceContent := range content {
applyModeColors(deviceContent, opts.Mode, opts.UseColor)
}
if opts.Mode == "speed" || opts.Mode == "power" || opts.Mode == "summary" {
renderSummary(summary, opts.Mode, opts.UseColor)
}
}
info2.txt
Certo! Aggiungo il supporto per flag brevi e combinati:Perfetto! Ora il programma supporta:
## ✅ Flag brevi:
- `-d` o `--default` - Modalità default
- `-r` o `--raw` - Output grezzo
- `-s` o `--speed` - Focus velocità
- `-p` o `--power` - Focus alimentazione
- `-P` o `--problems` - Solo problemi
- `-l` o `--location` - Mostra posizione
- `-S` o `--summary` - Statistiche
- `-n` o `--no-color` - Senza colori
- `-a` o `--ascii` - Caratteri ASCII
- `-h` o `--help` - Aiuto
## ✅ Flag combinati:
Puoi combinare più flag brevi insieme:
```bash
./usbi -sp # Speed + Power
./usbi -spl # Speed + Power + Location
./usbi -spS # Speed + Power + Summary
./usbi -spn # Speed + Power senza colori
./usbi -sPl # Speed + Problems + Location
```
## ✅ Modalità multiple:
Ora puoi attivare più modalità contemporaneamente e il programma mostrerà tutte le informazioni rilevanti con i colori appropriati per ogni modalità attiva.
**Esempio pratico:**
```bash
# Mostra sia velocità che consumo con colori specifici per entrambi
./usbi -sp
# Mostra velocità, consumo e posizione
./usbi -spl
# Tutto insieme con statistiche finali
./usbi -splS
```
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
)
// Core data structures
type USBDevice struct {
Name string
Level int
Speed string
Manufacturer string
VendorID string
ProductID string
PowerRequired int
PowerAvailable int
LocationID string
BusNumber string
DeviceNumber string
IsBus bool
BusDriver string
}
type DisplayOptions struct {
Modes map[string]bool // Support multiple modes at once
UseColor bool
UseASCII bool
}
type Summary struct {
DeviceCount int
HubCount int
TotalPowerUsed int
TotalPowerAvail int
ProblemCount int
SpeedCategories map[string]int
}
type DeviceContent struct {
Name string
Category string
HealthIndicators string
Speed string
SpeedCategory string
TransferRate string
Manufacturer string
VendorID string
PowerText string
PowerUsage float64
Location string
HasProblems bool
HasSpeedProblems bool
HasPowerProblems bool
IsHub bool
IsBus bool
BusDriver string
Level int
TreePrefix string
AttributeIndent string
}
// Color functions
func colorize(text, color string, useColor bool) string {
if !useColor {
return text
}
colors := map[string]string{
"red": "\033[31m",
"green": "\033[32m",
"yellow": "\033[33m",
"blue": "\033[34m",
"magenta": "\033[35m",
"cyan": "\033[36m",
"white": "\033[37m",
"bold": "\033[1m",
"dim": "\033[2m",
"reset": "\033[0m",
}
if code, exists := colors[color]; exists {
return code + text + colors["reset"]
}
return text
}
func red(text string, useColor bool) string { return colorize(text, "red", useColor) }
func green(text string, useColor bool) string { return colorize(text, "green", useColor) }
func yellow(text string, useColor bool) string { return colorize(text, "yellow", useColor) }
func blue(text string, useColor bool) string { return colorize(text, "blue", useColor) }
func cyan(text string, useColor bool) string { return colorize(text, "cyan", useColor) }
func white(text string, useColor bool) string { return colorize(text, "white", useColor) }
func bold(text string, useColor bool) string { return colorize(text, "bold", useColor) }
func dim(text string, useColor bool) string { return colorize(text, "dim", useColor) }
// Vendor database
func getVendorName(vendorID, originalName string) string {
vendorMap := map[string]string{
"18d1": "Google",
"05ac": "Apple",
"1532": "Razer",
"046d": "Logitech",
"8087": "Intel",
"2188": "CalDigit",
"1a40": "Terminus Tech",
"1a86": "QinHeng Electronics",
"0781": "SanDisk",
"0930": "Toshiba",
"152d": "JMicron",
"174c": "ASMedia",
"1058": "Western Digital",
"04e8": "Samsung",
"0bc2": "Seagate",
"1d6b": "Linux Foundation",
}
cleanID := strings.ToLower(strings.TrimPrefix(vendorID, "0x"))
if vendor, exists := vendorMap[cleanID]; exists {
return vendor
}
return originalName
}
// Device to USB standard mapping
func getDeviceMaxUSBStandard(deviceName, vendorID string) string {
nameLower := strings.ToLower(deviceName)
pixelDevices := map[string]string{
"pixel 9 pro": "USB 3.2",
"pixel 9": "USB 3.2",
"pixel 8 pro": "USB 3.2",
"pixel 8": "USB 3.0",
"pixel 7 pro": "USB 3.1",
"pixel 7": "USB 3.0",
"pixel 6 pro": "USB 3.1",
"pixel 6": "USB 3.0",
"pixel 5": "USB 3.0",
"pixel 4": "USB 3.0",
"pixel 3": "USB 2.0",
"pixel 2": "USB 2.0",
}
for deviceKey, usbStandard := range pixelDevices {
if strings.Contains(nameLower, deviceKey) {
return usbStandard
}
}
if strings.Contains(nameLower, "ssd") || strings.Contains(nameLower, "nvme") {
return "USB 3.1"
}
if strings.Contains(nameLower, "hdd") || strings.Contains(nameLower, "drive") {
return "USB 3.0"
}
if strings.Contains(nameLower, "mouse") || strings.Contains(nameLower, "keyboard") {
return "USB 2.0"
}
return "USB 2.0"
}
func getUSBStandardMaxSpeed(standard string) string {
speedMap := map[string]string{
"USB 1.0": "1.5 Mb/s",
"USB 1.1": "12 Mb/s",
"USB 2.0": "480 Mb/s",
"USB 3.0": "5 Gb/s",
"USB 3.1": "10 Gb/s",
"USB 3.2": "20 Gb/s",
"USB4": "40 Gb/s",
}
if speed, exists := speedMap[standard]; exists {
return speed
}
return "Unknown"
}
func isSpeedSuboptimal(deviceName, vendorID, currentSpeed string) bool {
maxStandard := getDeviceMaxUSBStandard(deviceName, vendorID)
currentStandard := categorizeSpeed(currentSpeed)
if maxStandard == "USB 2.0" || currentStandard == "" {
return false
}
standardOrder := map[string]int{
"USB 1.0": 1,
"USB 1.1": 2,
"USB 2.0": 3,
"USB 3.0": 4,
"USB 3.1": 5,
"USB 3.2": 6,
"USB4": 7,
}
maxOrder, maxExists := standardOrder[maxStandard]
currentOrder, currentExists := standardOrder[currentStandard]
if !maxExists || !currentExists {
return false
}
return currentOrder < (maxOrder - 1)
}
// Device categorization
func categorizeDevice(name, vendorID, productID string) string {
nameLower := strings.ToLower(name)
categories := map[string][]string{
"Hub": {"hub", "root hub"},
"Mouse/Trackpad": {"mouse", "trackpad", "touchpad"},
"Keyboard": {"keyboard"},
"Phone": {"phone", "pixel", "iphone", "android"},
"Storage": {"drive", "disk", "storage", "ssd", "hdd", "flash", "card reader"},
"Camera": {"camera", "webcam"},
"Audio": {"audio", "speaker", "headphone", "microphone", "sound"},
"Printer": {"printer"},
"Adapter": {"adapter", "dongle", "bluetooth"},
"Composite Device": {"composite"},
}
for category, keywords := range categories {
for _, keyword := range keywords {
if strings.Contains(nameLower, keyword) {
return category
}
}
}
return "Unknown Device"
}
// Speed categorization
func categorizeSpeed(speed string) string {
speedLower := strings.ToLower(speed)
if strings.Contains(speedLower, "1.5m") {
return "USB 1.0"
}
if strings.Contains(speedLower, "12m") {
return "USB 1.1"
}
if strings.Contains(speedLower, "480m") {
return "USB 2.0"
}
if strings.Contains(speedLower, "5000m") || strings.Contains(speedLower, "5g") {
return "USB 3.0"
}
if strings.Contains(speedLower, "10000m") || strings.Contains(speedLower, "10g") {
return "USB 3.1"
}
if strings.Contains(speedLower, "20000m") || strings.Contains(speedLower, "20g") {
return "USB 3.2"
}
if strings.Contains(speedLower, "40000m") || strings.Contains(speedLower, "40g") {
return "USB4"
}
return ""
}
func getTransferRate(speedCategory string) string {
rates := map[string]string{
"USB 1.0": "~0.2 MB/s max",
"USB 1.1": "~1.5 MB/s max",
"USB 2.0": "~60 MB/s max",
"USB 3.0": "~625 MB/s max",
"USB 3.1": "~1.25 GB/s max",
"USB 3.2": "~2.5 GB/s max",
"USB4": "~5 GB/s max",
}
return rates[speedCategory]
}
// Problem detection
func hasProblems(device USBDevice) bool {
if device.Name == "" || device.Speed == "" {
return true
}
if device.PowerRequired > 0 && device.PowerAvailable > 0 {
if device.PowerRequired > device.PowerAvailable {
return true
}
usage := float64(device.PowerRequired) / float64(device.PowerAvailable)
if usage > 0.95 {
return true
}
}
if isSpeedSuboptimal(device.Name, device.VendorID, device.Speed) {
return true
}
return false
}
func hasNonPowerProblems(device USBDevice) bool {
if device.Name == "" || device.Speed == "" {
return true
}
if device.PowerRequired > 0 && device.PowerAvailable > 0 {
if device.PowerRequired > device.PowerAvailable {
return true
}
}
if isSpeedSuboptimal(device.Name, device.VendorID, device.Speed) {
return true
}
return false
}
func hasPowerProblems(device USBDevice) bool {
if device.PowerRequired > 0 && device.PowerAvailable > 0 {
if device.PowerRequired > device.PowerAvailable {
return true
}
usage := float64(device.PowerRequired) / float64(device.PowerAvailable)
if usage > 0.95 {
return true
}
}
return false
}
func hasSpeedProblems(device USBDevice) bool {
return isSpeedSuboptimal(device.Name, device.VendorID, device.Speed)
}
func isHub(name string) bool {
nameLower := strings.ToLower(name)
return strings.Contains(nameLower, "hub") || strings.Contains(nameLower, "root hub")
}
// Tree rendering helpers
func getTreePrefix(level int, isLast bool, useASCII bool, treeContinues []bool) string {
if level == 0 {
return ""
}
var vert, branch, lastBranch string
if useASCII {
vert = "|"
branch = "+-"
lastBranch = "\\-"
} else {
vert = "│"
branch = "├─"
lastBranch = "└─"
}
prefix := ""
for i := 1; i < level; i++ {
if i < len(treeContinues) && treeContinues[i] {
prefix += vert + " "
} else {
prefix += " "
}
}
if isLast {
prefix += lastBranch + " "
if level < len(treeContinues) {
treeContinues[level] = false
}
} else {
prefix += branch + " "
if level < len(treeContinues) {
treeContinues[level] = true
}
}
return prefix
}
func getAttributeIndent(displayLevel int, isLast bool, useASCII bool, treeContinues []bool) string {
indent := ""
vert := "│"
if useASCII {
vert = "|"
}
for i := 1; i <= displayLevel; i++ {
if i < displayLevel && i < len(treeContinues) && treeContinues[i] {
indent += vert + " "
} else if i < displayLevel {
indent += " "
} else if !isLast {
indent += vert + " "
} else {
indent += " "
}
}
return indent
}
func isLastAtLevel(devices []USBDevice, currentIndex, level int, isBus bool) bool {
for i := currentIndex + 1; i < len(devices); i++ {
if devices[i].IsBus && !isBus {
break
}
if isBus {
if devices[i].IsBus {
return false
}
} else {
if devices[i].Level <= level {
if devices[i].Level == level {
return false
}
break
}
}
}
return true
}
// Parse lsusb output for Linux
func runSystemCommand() ([]USBDevice, error) {
// Check if lsusb is available
if _, err := exec.LookPath("lsusb"); err != nil {
return nil, fmt.Errorf("lsusb not found. Please install usbutils: sudo apt-get install usbutils")
}
// Get basic device list
cmd := exec.Command("lsusb")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to run lsusb: %v", err)
}
var devices []USBDevice
busMap := make(map[string]bool)
// Parse lsusb output
scanner := bufio.NewScanner(strings.NewReader(string(output)))
for scanner.Scan() {
line := scanner.Text()
// Format: Bus 001 Device 002: ID 8087:8000 Intel Corp.
re := regexp.MustCompile(`Bus (\d+) Device (\d+): ID ([0-9a-fA-F]{4}):([0-9a-fA-F]{4})(.*)`)
matches := re.FindStringSubmatch(line)
if len(matches) < 6 {
continue
}
busNum := matches[1]
devNum := matches[2]
vendorID := matches[3]
productID := matches[4]
description := strings.TrimSpace(matches[5])
// Add bus if not seen before
if !busMap[busNum] {
busMap[busNum] = true
devices = append(devices, USBDevice{
Name: fmt.Sprintf("USB Bus %s", busNum),
Level: 0,
IsBus: true,
BusNumber: busNum,
BusDriver: "Linux USB",
})
}
// Get detailed info for this device
detailCmd := exec.Command("lsusb", "-v", "-s", fmt.Sprintf("%s:%s", busNum, devNum))
detailOutput, err := detailCmd.Output()
var speed, manufacturer string
var powerRequired, powerAvailable int
if err == nil {
detailScanner := bufio.NewScanner(strings.NewReader(string(detailOutput)))
for detailScanner.Scan() {
detailLine := strings.TrimSpace(detailScanner.Text())
// Speed
if strings.Contains(detailLine, "bcdUSB") {
if strings.Contains(detailLine, "1.00") {
speed = "1.5 Mb/s"
} else if strings.Contains(detailLine, "1.10") {
speed = "12 Mb/s"
} else if strings.Contains(detailLine, "2.00") {
speed = "480 Mb/s"
} else if strings.Contains(detailLine, "3.00") {
speed = "5000 Mb/s"
} else if strings.Contains(detailLine, "3.10") {
speed = "10000 Mb/s"
}
}
// Manufacturer
if strings.HasPrefix(detailLine, "iManufacturer") {
parts := strings.SplitN(detailLine, " ", 3)
if len(parts) >= 3 {
manufacturer = strings.TrimSpace(parts[2])
}
}
// Power
if strings.Contains(detailLine, "MaxPower") {
re := regexp.MustCompile(`(\d+)mA`)
if m := re.FindStringSubmatch(detailLine); len(m) > 1 {
if val, err := strconv.Atoi(m[1]); err == nil {
powerRequired = val
powerAvailable = 500 // Default USB 2.0
if strings.Contains(speed, "5000") {
powerAvailable = 900 // USB 3.0
}
}
}
}
}
}
// Clean up description
if description == "" {
description = "Unknown Device"
}
// Use vendor name if available
if manufacturer != "" {
description = manufacturer + " " + description
} else {
vendorName := getVendorName(vendorID, "")
if vendorName != "" {
description = vendorName + " " + description
}
}
device := USBDevice{
Name: description,
Level: 1,
Speed: speed,
Manufacturer: manufacturer,
VendorID: vendorID,
ProductID: productID,
PowerRequired: powerRequired,
PowerAvailable: powerAvailable,
LocationID: fmt.Sprintf("Bus %s, Device %s", busNum, devNum),
BusNumber: busNum,
DeviceNumber: devNum,
IsBus: false,
}
devices = append(devices, device)
}
return devices, nil
}
// Generate unified content for all devices
func generateDeviceContent(devices []USBDevice, opts DisplayOptions) []DeviceContent {
var content []DeviceContent
treeContinues := make([]bool, 20)
for i, device := range devices {
if device.Name == "" {
continue
}
isLast := isLastAtLevel(devices, i, device.Level, device.IsBus)
if device.IsBus {
content = append(content, generateBusContent(device, opts))
} else {
content = append(content, generateSingleDeviceContent(device, isLast, opts, treeContinues))
}
}
return content
}
func generateBusContent(device USBDevice, opts DisplayOptions) DeviceContent {
return DeviceContent{
Name: device.Name,
BusDriver: device.BusDriver,
IsBus: true,
Level: device.Level,
}
}
func generateSingleDeviceContent(device USBDevice, isLast bool, opts DisplayOptions, treeContinues []bool) DeviceContent {
prefix := getTreePrefix(device.Level, isLast, opts.UseASCII, treeContinues)
attrIndent := getAttributeIndent(device.Level, isLast, opts.UseASCII, treeContinues)
category := ""
healthIndicators := ""
if !isHub(device.Name) {
category = categorizeDevice(device.Name, device.VendorID, device.ProductID)
speedCat := categorizeSpeed(device.Speed)
if speedCat == "USB 1.0" || speedCat == "USB 1.1" || speedCat == "USB 2.0" {
healthIndicators += " *"
}
if hasNonPowerProblems(device) && !hasSpeedProblems(device) && !hasPowerProblems(device) {
healthIndicators += " [Problem]"
}
}
var powerText string
var powerUsage float64
if device.PowerRequired > 0 && device.PowerAvailable > 0 {
powerUsage = float64(device.PowerRequired) / float64(device.PowerAvailable) * 100
powerText = fmt.Sprintf("Power: %dmA/%dmA [%.1f%%]", device.PowerRequired, device.PowerAvailable, powerUsage)
}
speedCat := categorizeSpeed(device.Speed)
speedText := ""
if device.Speed != "" {
speedText = fmt.Sprintf("Speed: %s", device.Speed)
if speedCat != "" {
speedText += fmt.Sprintf(" [%s]", speedCat)
}
maxStandard := getDeviceMaxUSBStandard(device.Name, device.VendorID)
shouldShowMax := false
nameLower := strings.ToLower(device.Name)
if strings.Contains(nameLower, "pixel") || strings.Contains(nameLower, "ssd") || strings.Contains(nameLower, "nvme") {
shouldShowMax = true
}
if maxStandard != "USB 2.0" && isSpeedSuboptimal(device.Name, device.VendorID, device.Speed) {
shouldShowMax = true
}
if shouldShowMax {
maxSpeed := getUSBStandardMaxSpeed(maxStandard)
if maxSpeed != "Unknown" {
speedText += fmt.Sprintf(" (max: %s)", maxSpeed)
}
}
}
transferRate := getTransferRate(speedCat)
return DeviceContent{
Name: device.Name,
Category: category,
HealthIndicators: healthIndicators,
Speed: speedText,
SpeedCategory: speedCat,
TransferRate: transferRate,
Manufacturer: device.Manufacturer,
VendorID: device.VendorID,
PowerText: powerText,
PowerUsage: powerUsage,
Location: device.LocationID,
HasProblems: hasProblems(device),
HasSpeedProblems: hasSpeedProblems(device),
HasPowerProblems: hasPowerProblems(device),
IsHub: isHub(device.Name),
IsBus: false,
Level: device.Level,
TreePrefix: prefix,
AttributeIndent: attrIndent,
}
}
// Mode-specific color application
func applyModeColors(content DeviceContent, opts DisplayOptions, useColor bool) {
if content.IsBus {
renderBusWithColors(content, opts, useColor)
return
}
displayName := content.Name
if content.IsHub {
displayName = bold(content.Name, useColor)
} else if content.Category != "" && !strings.Contains(content.Category, "Unknown") {
displayName = content.Category + " " + bold(content.Name, useColor)
} else {
displayName = bold(content.Name, useColor)
}
if content.HasProblems && strings.Contains(content.HealthIndicators, "[Problem]") {
healthPart := strings.Replace(content.HealthIndicators, "[Problem]", red("[Problem]", useColor), 1)
displayName = white(displayName, useColor) + healthPart
} else {
displayName = white(displayName+content.HealthIndicators, useColor)
}
fmt.Println(content.TreePrefix + displayName)
renderAttributesWithColors(content, opts, useColor)
}
func renderBusWithColors(content DeviceContent, opts DisplayOptions, useColor bool) {
busName := content.Name
if content.BusDriver != "" {
busName += " [" + content.BusDriver + "]"
}
fmt.Println(white(busName, useColor))
}
func renderAttributesWithColors(content DeviceContent, opts DisplayOptions, useColor bool) {
indent := content.AttributeIndent
// Determine which modes are active
showSpeed := opts.Modes["speed"] || opts.Modes["default"]
showPower := opts.Modes["power"] || opts.Modes["default"]
showLocation := opts.Modes["location"]
// Speed information
if content.Speed != "" && showSpeed {
line := content.Speed
if content.TransferRate != "" {
line += " " + dim(content.TransferRate, useColor)
}
// Apply coloring based on mode
if opts.Modes["speed"] {
switch content.SpeedCategory {
case "USB 1.0", "USB 1.1":
line = red(line, useColor)
case "USB 2.0":
line = yellow(line, useColor)
case "USB 3.0", "USB 3.1":
line = green(line, useColor)
case "USB 3.2", "USB4":
line = cyan(line, useColor)
default:
line = dim(line, useColor)
}
} else if opts.Modes["default"] {
// Default mode: show speed problems in red, otherwise dim
if content.HasSpeedProblems {
line = red(content.Speed+" "+content.TransferRate, useColor)
} else {
line = dim(content.Speed+" "+content.TransferRate, useColor)
}
} else {
line = dim(line, useColor)
}
fmt.Printf("%s%s\n", indent, line)
}
// Manufacturer and Vendor ID
if content.Manufacturer != "" {
fmt.Printf("%s%s %s\n", indent, dim("Manufacturer:", useColor), dim(content.Manufacturer, useColor))
}
if content.VendorID != "" {
fmt.Printf("%s%s %s\n", indent, dim("Vendor ID:", useColor), dim(content.VendorID, useColor))
}
// Power information
if content.PowerText != "" && showPower {
mainPart := fmt.Sprintf("Power: %dmA/%dmA",
extractPowerRequired(content.PowerText),
extractPowerAvailable(content.PowerText))
percentPart := fmt.Sprintf(" [%.1f%%]", content.PowerUsage)
var line string
if opts.Modes["power"] {
// Power mode: color by usage level
if content.PowerUsage > 90 {
line = red(mainPart, useColor) + dim(percentPart, useColor)
} else if content.PowerUsage > 50 {
line = yellow(mainPart, useColor) + dim(percentPart, useColor)
} else {
line = green(mainPart, useColor) + dim(percentPart, useColor)
}
} else if opts.Modes["default"] {
// Default mode: show power problems in red
if content.HasPowerProblems {
line = red(content.PowerText, useColor)
} else {
line = dim(content.PowerText, useColor)
}
} else {
line = dim(content.PowerText, useColor)
}
fmt.Printf("%s%s\n", indent, line)
}
// Location information
if content.Location != "" && (showLocation || opts.Modes["location"]) {
if opts.Modes["location"] {
fmt.Printf("%s%s %s\n", indent, dim("Location:", useColor), white(content.Location, useColor))
} else {
fmt.Printf("%s%s %s\n", indent, dim("Location:", useColor), dim(content.Location, useColor))
}
}
}
func extractPowerRequired(powerText string) int {
re := regexp.MustCompile(`(\d+)mA/`)
matches := re.FindStringSubmatch(powerText)
if len(matches) > 1 {
if val, err := strconv.Atoi(matches[1]); err == nil {
return val
}
}
return 0
}
func extractPowerAvailable(powerText string) int {
re := regexp.MustCompile(`/(\d+)mA`)
matches := re.FindStringSubmatch(powerText)
if len(matches) > 1 {
if val, err := strconv.Atoi(matches[1]); err == nil {
return val
}
}
return 0
}
func calculateSummary(devices []USBDevice) Summary {
summary := Summary{
SpeedCategories: make(map[string]int),
}
for _, device := range devices {
if device.IsBus {
continue
}
summary.DeviceCount++
if isHub(device.Name) {
summary.HubCount++
}
if device.PowerRequired > 0 {
summary.TotalPowerUsed += device.PowerRequired
}
if device.PowerAvailable > 0 {
summary.TotalPowerAvail += device.PowerAvailable
}
if device.Speed != "" {
speedCat := categorizeSpeed(device.Speed)
if speedCat != "" {
summary.SpeedCategories[speedCat]++
}
}
if hasProblems(device) {
summary.ProblemCount++
}
}
return summary
}
func renderSummary(summary Summary, opts DisplayOptions, useColor bool) {
fmt.Println()
// Show summary if summary mode is active
if opts.Modes["summary"] {
fmt.Printf("Total Devices: %d (excluding hubs)\n", summary.DeviceCount-summary.HubCount)
fmt.Printf("Hubs: %d\n", summary.HubCount)
fmt.Println()
if summary.ProblemCount == 0 {
fmt.Println(green("No USB problems detected!", useColor))
} else {
fmt.Printf("%s %d device(s) with problems\n", red("Found", useColor), summary.ProblemCount)
}
fmt.Println()
}
// Show legends for active modes
if opts.Modes["power"] {
renderPowerLegend(useColor)
if opts.Modes["speed"] {
fmt.Println()
}
}
if opts.Modes["speed"] {
renderSpeedLegend(useColor)
}
}
func renderPowerLegend(useColor bool) {
fmt.Println(blue("Power Legend:", useColor))
fmt.Printf(" %s - Low usage, efficient\n", green("< 50%", useColor))
fmt.Printf(" %s - Moderate usage, monitor\n", yellow("> 50%", useColor))
fmt.Printf(" %s - High usage, may cause issues\n", red("> 90%", useColor))
}
func renderSpeedLegend(useColor bool) {
fmt.Println(blue("Speed Legend:", useColor))
fmt.Printf(" %s - Very slow, legacy devices\n", red("USB 1.0/1.1", useColor))
fmt.Printf(" %s - Slower, older devices\n", yellow("USB 2.0", useColor))
fmt.Printf(" %s - Fast, modern devices\n", green("USB 3.0/3.1", useColor))
fmt.Printf(" %s - Very fast, latest devices\n", cyan("USB 3.2/USB4", useColor))
}
func printHelp() {
fmt.Println(`Usage: usbi [OPTIONS]
Options:
-d, --default Enhanced info with categories, vendor names, and health indicators (default)
-r, --raw Raw lsusb output
-s, --speed Speed-focused tree with actual values [category speed]
-p, --power Power-focused tree with usage warnings [req/avail mA]
-P, --problems Show only devices with issues (power, speed, driver problems)
-l, --location Show location info along with manufacturer details
-S, --summary Port utilization, power totals, device counts
-n, --no-color Disable colored output
-a, --ascii Use ASCII characters for tree (instead of Unicode)
-h, --help Show this help message
Examples:
usbi # Default mode
usbi -s # Speed mode
usbi -sp # Speed + Power mode (combined view)
usbi -sl # Speed + Location mode
usbi -spn # Speed + Power without colors
Color thresholds (power): red >90%, yellow >50%, green otherwise
Requirements:
- lsusb (install with: sudo apt-get install usbutils)
- May require sudo for detailed device information`)
}
func main() {
opts := DisplayOptions{
Modes: make(map[string]bool),
UseColor: true,
UseASCII: false,
}
// If no arguments, use default mode
if len(os.Args) == 1 {
opts.Modes["default"] = true
}
// Parse command line arguments
for _, arg := range os.Args[1:] {
// Handle long flags
switch arg {
case "--default", "-d":
opts.Modes["default"] = true
case "--raw", "-r":
opts.Modes["raw"] = true
case "--speed", "-s":
opts.Modes["speed"] = true
case "--power", "-p":
opts.Modes["power"] = true
case "--problems", "-P":
opts.Modes["problems"] = true
case "--location", "-l":
opts.Modes["location"] = true
case "--summary", "-S":
opts.Modes["summary"] = true
case "--no-color", "-n":
opts.UseColor = false
case "--ascii", "-a":
opts.UseASCII = true
case "--help", "-h":
printHelp()
return
default:
// Handle combined short flags (e.g., -sp, -spl)
if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") {
flags := arg[1:] // Remove the leading dash
for _, flag := range flags {
switch flag {
case 'd':
opts.Modes["default"] = true
case 'r':
opts.Modes["raw"] = true
case 's':
opts.Modes["speed"] = true
case 'p':
opts.Modes["power"] = true
case 'P':
opts.Modes["problems"] = true
case 'l':
opts.Modes["location"] = true
case 'S':
opts.Modes["summary"] = true
case 'n':
opts.UseColor = false
case 'a':
opts.UseASCII = true
case 'h':
printHelp()
return
default:
fmt.Fprintf(os.Stderr, "Unknown flag: -%c\n", flag)
fmt.Fprintf(os.Stderr, "Use -h or --help for usage information\n")
os.Exit(1)
}
}
} else {
fmt.Fprintf(os.Stderr, "Unknown option: %s\n", arg)
fmt.Fprintf(os.Stderr, "Use -h or --help for usage information\n")
os.Exit(1)
}
}
}
// Raw mode overrides everything
if opts.Modes["raw"] {
opts.Modes = map[string]bool{"raw": true}
}
// If no mode selected (only color/ascii flags), use default
if len(opts.Modes) == 0 {
opts.Modes["default"] = true
}
// Disable colors if output is not to a terminal
if opts.UseColor {
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
opts.UseColor = false
}
}
// Handle raw mode separately
if opts.Modes["raw"] {
cmd := exec.Command("lsusb")
output, err := cmd.Output()
if err != nil {
fmt.Fprintf(os.Stderr, "Error running lsusb: %v\n", err)
os.Exit(1)
}
fmt.Print(string(output))
return
}
// Parse USB devices
devices, err := runSystemCommand()
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing USB data: %v\n", err)
os.Exit(1)
}
// Filter for problems mode
if opts.Modes["problems"] {
var filteredDevices []USBDevice
for _, device := range devices {
if device.IsBus || hasProblems(device) {
filteredDevices = append(filteredDevices, device)
}
}
devices = filteredDevices
}
// Generate unified content
content := generateDeviceContent(devices, opts)
summary := calculateSummary(devices)
// Render devices
fmt.Println()
for _, deviceContent := range content {
applyModeColors(deviceContent, opts, opts.UseColor)
}
// Render summary/legends if any relevant mode is active
if opts.Modes["speed"] || opts.Modes["power"] || opts.Modes["summary"] {
renderSummary(summary, opts, opts.UseColor)
}
}
Nel mio caso faccio qualcosa del genere. Collego il pc ad un cell tramite cavo 1 e lo misuro e poi variando i cavi ed ovviamente rifacendo le misurazioni si notano le differenze
Spero che il codice non sia troppo maltrattato da wp