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

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! 😉

Il mistero dei file ics comparsi

Rovistando tra i file presenti sul mio cellulare mi son imbattito in una serie di file con nomi tipo Eventi_20250326_063351.ics. Sono i file di calendar o nel mio caso i file di etar. Visto che erano 130 file e non avevo appuntamenti particolari da ricordare li ho eliminati mettendoli tutti in una cartella e passando la cartella sul mio pc. Tutto ok ma cosa cavolo ci sarà su tutti quei file?

I file ics si aprono tranquillamente con un editor di testo tipo pluma o gedit e simili ma aprirli uno ad uno aveva veramente poco senso e di conseguenza proviamo a chiedere all IA

Il risultato son due script il primo parsa tutti i file e mette i dati importani in un file estratti.txt

Il secondo parsa tutti i file ics della cartella ma se ci sono eventi ripetuti li segna una volta sola

https://gitlab.com/ugone-git/parsafileics/-/blob/19b3decbf824d1e3585af63997eaa77615b5cfd5/parsaics.sh

https://gitlab.com/ugone-git/parsafileics/-/blob/19b3decbf824d1e3585af63997eaa77615b5cfd5/parsaics2,sh

Info varie

Molte volte sento dire che linux è difficile perchè ci son quei comandi strani ecc ecc.
Non c’è nulla come il non conoscere una cosa per farla sembrare difficile ed astrusa.
Internet è pieno di guide più o meno riuscite come far quel che serve ma ogni volta devi cercare e perdere tempo.
Benissimo, cerchiamo di risolvere questi problemi una volta per tutte.
Facciamo un esempio proprio elementare
Vorrei aggiornare il sistema ma non so come fare. Vado su un motore di ricerca e faccio una ricerca tipo questa > debian aggiornare il sistema < ed in vari siti vedremo che uno dei vari sistemi è di mettere in un terminale questo
sudo apt update
sudo apt upgrade

benissimo solo che da una volta all altra che aggiorno il sistema non mi ricordo come posso fare?

ci sono parecchi sistemi ma qui ne vedremo solo 2
1) visto che questo è una cosa estremamente breve e semplice potremmo creare un semplice script bash che una volta cliccato ci chiede la password ed aggiorna
per farlo basta aprire ad esempio pluma o gedit o kate o comunque un editor di testo semplice (non va bene un editor tipo libreoffice perchè inserisce stili e formattazioni)
e dentro scrivere

#!/bin/bash

#
sudo apt update && sudo apt upgrade -y

poi salvare il file come aggiorna.sh in un posto comodo sul pc e controllare che sia eseguibile. quando vorrete aggiornare vi basterà lanciarlo e dopo inserita la password fa quel che serve
spiegazione dello script

#!/bin/bash

#
molti script iniziano con #!/bin/bash per indicare al sistema operativo quale interprete utilizzare per eseguire il file. Questa linea è chiamata “shebang” e serve a specificare il percorso dell’interprete che deve essere utilizzato per eseguire lo script.

sudo apt update && sudo apt upgrade -y

questo è il comando vero e proprio e per esser più precisi è formato in parti
sudo apt update che è il comando che controlla se ci sono aggiornamenti
&& che serve a concatenare il primo comando al secondo
sudo apt upgrade che fa l’aggiornamento vero e proprio
-y che significa yes (assume che alle domande che potrebbero esserci nell aggiornamento la risposta sia si)
A grandi linee questo è il primo sistema ma lo stesso risultato lo possiamo ottenere con il secondo sistema che è usare un alias per fare le stesse cose
2) Guardiamo se nella home del nostro utente c’è un file chiamato .bash_aliases e se non ci fosse lo possiamo creare dando in un terminale touch .bash_aliases
poi con l’editor lo apriamo e scriviamo questo

alias souba=’source ~/.bashrc’
alias aggiorna=’sudo apt update && sudo apt upgrade -y’

e salviamo
in un terminale diamo source ~/.bashrc (il comando source ~/.bashrc viene utilizzato per ricaricare il file di configurazione .bashrc nella shell corrente senza dover chiudere e riaprire il terminale e senza dover riavviare il pc).
A questo punto abbiamo due nuovi comandi souba che vi servirà solo quando aggiungerete comandi ed aggiorna che vi aggiornerà il sistema
Tra parentesi se non vi piace souba potete metterci pippo o pluto o quel che vi piace l’importante e che i comandi seguano questo schema
alias nomescelto=’comando’
ps un piccolo trucchetto potrebbe esser questo. Nel file .bash_aliases sotto i comandi che usiamo possiamo aggiungere queste righe

nomescelto=’comando’

nomescelto=’comando’

nomescelto=’comando’

nomescelto=’comando’

nomescelto=’comando’

quando dovete aggiungere un alias vi basta modificarlo come serve e togliere # all inizio della riga 😉
(se avete 20 anni vi sembrano cose inutili e stupide ma se avete tante volte 20 anni vedrete che tornano utili un sacco di volte 😉

Installare rustdesk via ssh

Magari serva a qualcun altro. Dunque il problema che ho avuto è stato questo.
Un vecchio portatile che però va ancora discretamente (con batteria praticamente morta ma visto che sta attaccato alla corrente va bene lo stesso). Decido di metterlo in garage
Visto che va a riposo (forse) formatto l’ssd e reinstallo ex novo una bella debian 12 mate ed abilito ssh. Faccio le mie brave prove ed ssh va perfettamente. Giusto per scrupolo guardo che funzioni anche il wakeonlan e visto che funziona collego il tuttu dove va e spengo il tutto.
Arrivato in casa mi viene in mente ma se ci mettessi anche rustdesk per un eventuale controllo remoto? ottima idea e per farlo però via ssh ci vogliono un po di passaggi.
Vediamo di fare un passo passo

Prima di tutto bisogna andare sul sito di rustdesk e veder qual è l’ultima versione. Attualmente è la 138 e si prende il link del pacchetto da scaricare in questo caso era

https://github.com/rustdesk/rustdesk/releases/download/1.3.8/rustdesk-1.3.8-x86_64.deb. Copiato il link si va sul terminale connesso via ssh e si da

wget https://github.com/rustdesk/rustdesk/releases/download/1.3.8/rustdesk-1.3.8-x86_64.deb.

una volta scaricato bisogna installarlo e lo si fa con

sudo dpkg -i rustdesk-1.3.8-x86_64.deb
NOTA BENE molto probabilmente darà degli errori e dirà che non è configurato perchè mancano delle librerie.


dpkg: problemi con le dipendenze impediscono la configurazione di rustdesk:
rustdesk dipende da libxdo3; tuttavia:
Il pacchetto libxdo3 non è installato.
rustdesk dipende da gstreamer1.0-pipewire; tuttavia:
Il pacchetto gstreamer1.0-pipewire non è installato.


per cui dare

sudo apt install libxdo3 gstreamer1.0-pipewire

e successivamente visto che dice


sudo apt install libxdo3 gstreamer1.0-pipewire
Lettura elenco dei pacchetti… Fatto
Generazione albero delle dipendenze… Fatto
Lettura informazioni sullo stato… Fatto
È utile eseguire “apt –fix-broken install” per correggere ciò.
I seguenti pacchetti hanno dipendenze non soddisfatte:
gstreamer1.0-pipewire : Dipende: pipewire (= 0.3.65-3+deb12u1) ma non sta per essere installato
E: Dipendenze non soddisfatte. Provare “apt –fix-broken install” senza pacchetti (o specificare una soluzione).


diamo

sudo apt –fix-broken install
e quando ha finito dovremmo esser pronti

i due link che mi hanno aiutato sono
https://wiki.ubuntu-it.org/InternetRete/DesktopRemoto/RustDesk
ed
https://rustdesk.com/docs/en/client/


ma facendo un riassunto totale l’essenziale è

Servizio Comando

Avvio sudo systemctl start rustdesk.service

Arresto sudo systemctl stop rustdesk.service

Riavvio sudo systemctl restart rustdesk.service

Ricaricamento sudo systemctl reload rustdesk.service

Abilitazione sudo systemctl enable rustdesk.service

Disabilitazione sudo systemctl disable rustdesk.service

Blocco sudo systemctl mask rustdesk.service

Sblocco sudo systemctl unmask rustdesk.service

ed
Command Line Parameters

--password can be used to set a permanent password.
--get-id can be used to retrieve the ID.
--set-id can be used to set an ID, please note IDs should start with a letter.
--silent-install can be used to install RustDesk silently on Windows.

Per cui la sequenza da dare è


sudo systemctl start rustdesk.service
sudo systemctl enable rustdesk.service


a questo punto controllare se è tutto a posto con
systemctl -a | grep rustdesk.service


e dovrebbe rispondere


rustdesk.service
loaded active running RustDesk

siamo quasi pronti. Ora bisogna impostare la password e lo facciamo con


sudo rustdesk –password password-scelta-ma-bella-lunga


ed ottenere l’id con


rustdesk –get-id

ok tutto pronto ora per via grafica mettete l’id ottenuto e la pass che avete scelto e siete dentro.

bash_aliases

In Linux molto spesso si scoprono dei comandi utilissimi ma che magari son lunghi e non si usano spesso. Il problema principale è che usandoli raramente me li dimentico (ed a dimenticarmi le cose son un vero esperto).

Un ottimo sistema per ovviare al problema (se il comando lo permette) è crearsi un alias da mettere nel file .bash_aliases nella home. Qui di seguito un microesempio del possibile contenuto.i

alias souba=’source ~/.bashrc’
alias spegniti=’sudo shutdown -h now’
alias riavvia=’sudo reboot’
alias aggiorna=’sudo apt update && sudo apt full-upgrade -y’
alias aggiornadi=’sudo do-release-upgrade’
alias aggiosist=’sudo do-release-upgrade -d’
alias aremo=’sudo apt autoremove’
alias ssshed=’ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C “$(whoami)@$(hostname)”‘
alias rimuovilog=’sudo journalctl –vacuum-size=20M && sudo journalctl –vacuum-time=2d && sudo systemctl restart systemd-journald’

ovviamente potete aggiungere tutti gli alias che vi possono servire
se ne dovete aggiungere altri vi basta scriverli rispettando questa forma
alias nome_comando=’comando vero e proprio’ e dopo aver salvato bash_aliases o riavviate il pc (non ne vale la pena) o date source ~/.bashrc nel terminale o se l’avete fatto già almeno una volta vi basta dare souba (nome casuale inventato al momento perchè non sapevo che metterci 🙂

Se vi dimenticate anche degli alias che avete fatto vi basta aprire un terminale e digitare alias e vi appare la lista di tutti quelli che avete

Breve video su come cambiare icone delle cartelle

Breve video su come cambiare icone delle cartelle in debian Mate ma dovrebbe esser valido quasi sempre in linux.

Quando ci sono molte cartelle averle tutte uguali potrebbe essere un problema se non si ricorda più il nome. Conviene allora modificare il disegno della cartella in modo sia facilmente riconoscibile.

Il vero limite sarà solo la vostra fantasia nel scegliere l’immagine che più si adatta a quel che dovete fare

il video lo trovate qui https://peertube.uno/w/iw2EYWTigzBpVzQ8B1nSSx

Linux Day 2024

Viale Matteotti 31, Imperia
https://osm.org/go/xXaeH2ZGF?m=

Anche quest’anno siamo felici di proporre ad Imperia il giorno del Linux Day!

L’evento è gratuito e si terrà sabato 26 ottobre 2024 a Imperia Porto Maurizio, in viale Matteotti 31, dalle ore 10:00 alle 13:00 e poi, dopo la pausa pranzo, dalle 15:00 alle 18:00.

Durante la manifestazione si alterneranno una serie di talk:

Mattina

  • 10:00 Apertura
  • 10:30 Presentazione ILS Imperia
  • 11:00 → 11:50 “Linux contro Windows 2024”: un confronto tra le principali differenze con pro e contro nell’uso tra windows e linux nel 2024. Livello BASE, presentato da Stefano Semeria
  • 12:00 → 12:50 “Licenze software, queste sconosciute”: panoramica sui principali tipi di licenze software e linee guida per la distribuzione del software. Livello INTERMEDIO, presentato da Massimo Gismondi

Pomeriggio

  • 15:00 → 15:50 “Just for fun – divertiamoci programmando”: tuffiamoci nel mondo del recreational programming. Livello AVANZATO, presentato da Andrea Valenzano
  • 16:00 → 16:50 “Creiamo un videogioco: primi passi con Godot Engine”: panoramica introduttiva per utenti esperti a Godot Engine. Livello AVANZATO, presentato da Massimo Gismondi
  • 17:00→ 18:00 Saluti e Conclusione

Durante tutto l’evento si potrà portare il proprio computer o una chiavetta USB per provare o installare un sistema Linux.

rustdesk

Spesso mi capita di affrontare problemi dalla parte sbagliata ed ovviamente di non trovare soluzioni.
Mi ero messo in testa di mettere da remoto rustdesk su una macchina e senza dilungarmi non ci riuscivo.
Onde evitare di sbattere la testa contro i muri inutilmente vi posto la semplicissima soluzione (della serie avevo la soluzione davanti al naso e non la vedevo)
Per prima cosa do per scontato ci sia l’accesso ssh alla macchina remota.
ci connettiamo alla macchina remota via ssh e poi sulla macchina remota scarichiamo il deb (adattare alla propria distro) che serve.
Attualmente verrebbe cosi wget https://github.com/rustdesk/rustdesk/releases/download/1.3.1/rustdesk-1.3.1-x86_64.deb ed una volta scaricato dare
sudo apt install -fy ./rustdesk-1.3.1-x86_64.deb --fix-missing
Una volta installato dare rustdesk --get-id per aver l’id remoto e poi
sudo rustdesk --password passwordsceltaechesiabellalunga
A questo punto rimane solo da aprire rustdesk sulla propira macchina e mettere l’id remoto e la passwordsceltaechesiabellalunga
Con questo sistema se si riavvia la macchina rimane attivo il servizio di rustdesk. di conseguenza
Servizio Comando

Avvio sudo systemctl start rustdesk.service

Arresto sudo systemctl stop rustdesk.service

Riavvio sudo systemctl restart rustdesk.service

Ricaricamento sudo systemctl reload rustdesk.service

Abilitazione sudo systemctl enable rustdesk.service

Disabilitazione sudo systemctl disable rustdesk.service

Blocco sudo systemctl mask rustdesk.service

Sblocco sudo systemctl unmask rustdesk.service

nella situazione iniziale si disabilita il servizio con sudo systemctl disable rustdesk.service e poi si termina con sudo systemctl stop rustdesk.service

Per semplificare la questione direi che conviene mettere nel file .bash_aliases nella home queste righe
alias avviaru='sudo systemctl start rustdesk.service'
alias distop='sudo systemctl disable rustdesk.service && sudo systemctl stop rustdesk.service'

Dopo il riavvio della macchina remota (o dopo aver dato source ~/.bashrc ) dopo esservi collegati darete avviaru per avviare il servizio e distop per disabilitare il servizio e per fermarlo.