Misure usb

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. il risultato è nel codice seguente.

Troverete di seguito info1.txt poi usbi.go

#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)
	}
}

La seguente parte è info2.txt ed il relativo usbi.go da compilare come da info1.txt.

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)
	}
}

La seconda versione supporta anche le abbreviazioni tipo /usbi -splS Probabilmente c’è spazio per molte altre migliorie ma la mia sessione di claude gratuita era terminata 😉

PS ovviamente le misure che faccio si riferiscono sempre allo stesso oggetto messo a valle.

PC sempre la stessa porta/cavo da misurare/oggetto conosciuto che per i test era il mio cell

PS2 quando si lancia il comando impiega parecchio a dare le risposte per cui attendere circa un minuto prima di pensare che qualcosa non funziona . Una misura a spanne diceva

confermo circa 48 sec real  0m48,631s
user  0m0,001s
sys  0m0,008s

Come cambiare la password del proprio utente (e quella di root)

Se siete come me, può spesso capitare di dimenticare la password del proprio utente… specie su sistemi che non utilizzate spesso. Come possiamo modificare la password di root o quella del proprio utente?

Se ricordate la password di root, potete sempre cambiare la password di un utente X aprendo una shell e lanciando il comando seguente:

passwd X

dove X è il nome del vostro utente.

Ma cosa fare se non ricordate la password di root? E’ possibile modificare la password di root accedendo ad un ambiente live, ma esiste fortunatamente un’alternativa più pratica (perlomeno se usate GRUB come bootloader come la stragrande maggioranza delle distro).

Alla comparsa del menu di GRUB, premere il tasto'e' della tastiera.

Comparirà il contenuto del file di configurazione di GRUB. Ricercate una linea come questa:

linux /boot/vmlinuz-5.15.0-153-generic root=UUID=87c090d5-1900-48b4-a87a-724b0b09a7d4 ro quiet splash

Modificatela aggiungendo init=/bin/bash subito dopo ro quiet . Otterrete una cosa tipo:

linux /boot/vmlinuz-5.15.0-153-generic root=UUID=87c090d5-1900-48b4-a87a-724b0b09a7d4 ro quiet init=/bin/bash

Con questa modifica stiamo in pratica dicendo al kernel linux di inizializzare il sistema e al termine di avviare una shell bash (con privilegi di root) al posto del tradizionale programma che si occupa di continuare col normale avvio… in pratica una shell di emergenza da cui potete modificare un po’ quello che volete.

Dal momento che il kernel monta il filesystem readonly in fase di avvio, occorre eseguire il comando

mount -no remount,rw /                                                                                                                                                                                                                                                                                           

per poter effettuare delle modifiche.

A questo punto potete cambiare la password di un utente di nome bob con il comando:

passwd bob

Potete cambiare la password di root semplicemente col comando

passwd

A questo punto riavviate il PC con

reboot -f

Et voilat!

Per chi non ha problemi con l’inglese, la fonte che ha ispirato questo articolo è qui.

usare keepassXC o keepassDX

È inutile! Non possiamo ricordare tutte le password che abbiamo! Ci serve un Password Manager
Iniziamo dalla base. Do per scontato che usiate linux. Le cose dette son valide per tutti ma non è detto funzionino allo stesso modo su altri sistemi operativi
Un sistema per creare una buona password è una serie casuale di numeri,lettere ed altro MA ricordarle è quantomeno improbabile
Una password come Wgre38ztRcvYN#y@s6 potrebbe essere validissima ma o vi dovere ricordare solo questa o vi chiamate Pico della Mirandola (https://it.wikipedia.org/wiki/Giovanni_Pico_della_Mirandola)
Un buon sistema per aver una password buona e “facile da ricordare” è semplicemente una frase di varie parole casuali attaccate tipo cavalloverdedinonnobalengo e se ci mettete anche numeri, maiuscole e segni diventa perfetta tipo caValloverd3din@nnob4lengo
Altra cosa che aiuta a creare password molto robuste è tener premuto il tasto Alt Gr per cui la password cavalloverdedinonnobalengo diventa ¢æ“æłłø“€¶ð€ð→ñøññø”æł€ñŋø
Cmq la password dovrebbe essere almeno 15 caratteri ed ovviamente deve essere diversa per ogni cosa (= 100 siti 100 password diverse)
È facile vedere che la cosa si complica immediatamente e di parecchio!
Di conseguenza un buon gestore di password si impone !
Personalmente uso su pc KeepassXC (https://keepassxc.org/download/#linux) e su cell android (da fdroid) KeepassDX (https://f-droid.org/it/packages/com.kunzisoft.keepass.libre/) lo trovate anche su github e googleplay ma penso sia meglio usare fdroid quando si può.

Piccola divagazione circa i password manager. Ne trovate moltissimi, sicurissimi eccetera MA i password manager che salvano le cose in cloud vuol dire che senza internet non vanno o possono aver problemi, quelli proprietari di solito fanno solo gli interessi del proprietario che se vi dice da domani mi dai x per le tue password gli dovete dire si . Comunque scegliete molto attentamente. Questo esempio si basa su keepass solo perchè è quello che ho scelto ma bene o male funziona con tutti


Installazione
https://keepassxc.org/download/#linux
è più che semplice e basta seguire le guide (tra le altre cose oltre che a flatpak lo trovate anche come appimage per cui niente scuse)

se usate flatpak in un terminale date prima
flatpak remote-add –user –if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
e poi
flatpak install –user flathub org.keepassxc.KeePassXC

date y quando richiesto ed è installato

Se usate l appimage vi basta scaricare il file che trovate sotto altri download

e renderlo eseguibile (click destro sopra il file scaricato /proprietà/permessi e selezionare la casellina che dice Consenti l esecuzione del file come programma e poi un doppio click per aprire l appimage.

Primi passi appena installato
Supponendo usiate debian mate ( e se vi ho installato il sistema io è estremamente probabile che sia cosi) lo troverete in Applicazioni/Accessori/KeepassXC
un click sopra e si apre
la prima volta che si apre bisogna creare il database (le successive o lo si apre o lo si importa ma vedremo dopo)cliccando su crea il database appare la seguente immagine

e dovete dare un nome al database ed una breve descrizione. fate come preferite ma personalmente do come nome la data attuale tipo 25agosto2025.
nella schermata successiva potete lasciare come è impostato ed infine arriviamo alla scelta della password che dovete assolutamente ricordare (persa queste perso tutto per sempre)
Lo ripeto una password lunga e buona. Conviene usare solo lettere e numeri perchè potreste doverla digitare da dispositivi “strani” e le password con Alt Gr da cellulare sono un casino.

salvatevi la password in un pizzino o scrivetela in un luogo molto sicuro
Notare che in fondo alla casella c’è un quadratino che serve a generare le password. cmq lo ripeto password molto buona e salvatela in un posto molto sicuro.
Vi appare la pagina vuota dove iniziare a mettere le password. potete cliccare sul + nel cerchio in alto o premere ctrl+n o cliccare con il destro e scegliere nuova voce


la schermata che si apre è autoesplicativa e dovete mettere i dati richiesti.
Notare che mentre scriverete la password sotto appare una riga colorata che indica la sicurezza e la forza. Inutile dire che dovrebbe farla venire verde.
Potete dare un icona e creare gruppi di password (siti, accesso a macchine remote, credenziali varie, codici per banche eccetera)
Una volta che avrete messo tutte le password salvate il database. Questo database può esser passato anche sul cellulare se avete installato KeePassXC. Invece che ricrearlo potete semplicemente importarlo. Appena aperto keepass dx ha due pulsanti in basso uno che dice crea nuova cassaforte e l altro che dice apri cassaforte esistente. Per cui voi aprite il file.kdbx che avete salvato sul pc mettendo la password che avete scelto et voilà avete tutte le password anche su cell.

Infine potete unire i database se ad esempio sul cell avete aggiunto altre trenta nuove password salvate il database ed importatelo unendolo a quello del pc cosi avrete un nuovo db con tutte le varie password

Mc,Gnome Commander e Double Commander

Abbiamo a disposizione strumenti straordinari, ma spesso ci accontentiamo di soluzioni meno efficienti. Partiamo subito con un vero e proprio “carroarmato”: mc.

mc (Midnight Commander) è un programma fenomenale che lavora direttamente nel terminale, con una doppia finestra affiancata. Ecco alcuni esempi pratici per capire quanto sia potente:

  • Spostare file: basta aprire la prima finestra per la sorgente e la seconda per la destinazione. Facile e veloce.
  • Cambiare i permessi: selezionate il file, poi andate su “File” > “Permessi”. Semplice e intuitivo.
  • Copiare, spostare o eliminare file su un PC remoto: nella finestra destra (o sinistra) connettetevi tramite shell e il gioco è fatto.

In sostanza, mc è la soluzione per quasi tutto!

Per avere un’idea delle sue potenzialità, aprite Synaptic, cercate “mc” e date un clic destro sul nome. Guardate i pacchetti consigliati: c’è davvero tantissimo materiale da installare, probabilmente per settimane di utilizzo! 😉

Se dovete, ad esempio, connettervi a un PC remoto, basta scrivere nel terminale:

mc /sh://utente@host:porta

Ad esempio:

mc /sh://pi@192.168.5.8:2222

(se la porta SSH è diversa dalla 22, ed è sempre meglio che lo sia).

Il manuale di mc (che dovrebbe essere in italiano) è davvero completo e vi fornirà tutte le informazioni necessarie per sfruttarlo al massimo.

Se proprio non vi piace l’idea di usare il terminale e cercate una soluzione con interfaccia grafica, ci sono anche altre ottime alternative:

  1. GNOME CommanderSito ufficiale: un programma completo che offre molte funzionalità avanzate.
  2. Double CommanderSito ufficiale: anch’esso molto potente, anche se un po’ inferiore a mc e GNOME Commander in termini di completezza.

Personalmente, preferisco mc, ma credo che la scelta dipenda molto da come usate il PC (per me, terminale ed SSH sono tutto).

In ogni caso, strumenti come questi dovrebbero essere preinstallati su ogni distribuzione Linux! 😉