package sysinfo import ( "fmt" "os/exec" "regexp" "runtime" "strconv" "strings" "git.leaktechnologies.dev/stu/VideoTools/internal/logging" "git.leaktechnologies.dev/stu/VideoTools/internal/utils" ) // HardwareInfo contains system hardware information type HardwareInfo struct { CPU string `json:"cpu"` CPUCores int `json:"cpu_cores"` CPUMHz string `json:"cpu_mhz"` GPU string `json:"gpu"` GPUDriver string `json:"gpu_driver"` RAM string `json:"ram"` RAMMBytes uint64 `json:"ram_mb"` OS string `json:"os"` Arch string `json:"arch"` } // Detect gathers system hardware information func Detect() HardwareInfo { info := HardwareInfo{ OS: runtime.GOOS, Arch: runtime.GOARCH, CPUCores: runtime.NumCPU(), } // Detect CPU info.CPU, info.CPUMHz = detectCPU() // Detect GPU info.GPU, info.GPUDriver = detectGPU() // Detect RAM info.RAM, info.RAMMBytes = detectRAM() return info } // detectCPU returns CPU model and clock speed func detectCPU() (model, mhz string) { switch runtime.GOOS { case "linux": return detectCPULinux() case "windows": return detectCPUWindows() case "darwin": return detectCPUDarwin() default: return "Unknown CPU", "Unknown" } } func detectCPULinux() (model, mhz string) { // Read /proc/cpuinfo cmd := exec.Command("cat", "/proc/cpuinfo") output, err := cmd.Output() if err != nil { logging.Debug(logging.CatSystem, "failed to read /proc/cpuinfo: %v", err) return "Unknown CPU", "Unknown" } lines := strings.Split(string(output), "\n") for _, line := range lines { if strings.HasPrefix(line, "model name") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { model = strings.TrimSpace(parts[1]) } } if strings.HasPrefix(line, "cpu MHz") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { mhzStr := strings.TrimSpace(parts[1]) if mhzFloat, err := strconv.ParseFloat(mhzStr, 64); err == nil { mhz = fmt.Sprintf("%.0f MHz", mhzFloat) } } } // Exit early once we have both if model != "" && mhz != "" { break } } if model == "" { model = "Unknown CPU" } if mhz == "" { mhz = "Unknown" } return model, mhz } func detectCPUWindows() (model, mhz string) { // Use wmic to get CPU info cmd := exec.Command("wmic", "cpu", "get", "name,maxclockspeed") utils.ApplyNoWindow(cmd) // Hide command window on Windows output, err := cmd.Output() if err != nil { logging.Debug(logging.CatSystem, "failed to run wmic cpu: %v", err) return "Unknown CPU", "Unknown" } lines := strings.Split(string(output), "\n") if len(lines) >= 2 { // Parse the second line (first is header) fields := strings.Fields(lines[1]) if len(fields) >= 2 { mhzStr := fields[len(fields)-1] // Last field is clock speed model = strings.Join(fields[:len(fields)-1], " ") if mhzInt, err := strconv.Atoi(mhzStr); err == nil { mhz = fmt.Sprintf("%d MHz", mhzInt) } } } if model == "" { model = "Unknown CPU" } if mhz == "" { mhz = "Unknown" } return model, mhz } func detectCPUDarwin() (model, mhz string) { // Use sysctl to get CPU info cmdModel := exec.Command("sysctl", "-n", "machdep.cpu.brand_string") if output, err := cmdModel.Output(); err == nil { model = strings.TrimSpace(string(output)) } cmdMHz := exec.Command("sysctl", "-n", "hw.cpufrequency") if output, err := cmdMHz.Output(); err == nil { if hz, err := strconv.ParseUint(strings.TrimSpace(string(output)), 10, 64); err == nil { mhz = fmt.Sprintf("%.0f MHz", float64(hz)/1000000) } } if model == "" { model = "Unknown CPU" } if mhz == "" { mhz = "Unknown" } return model, mhz } // detectGPU returns GPU model and driver version func detectGPU() (model, driver string) { switch runtime.GOOS { case "linux": return detectGPULinux() case "windows": return detectGPUWindows() case "darwin": return detectGPUDarwin() default: return "Unknown GPU", "Unknown" } } func detectGPULinux() (model, driver string) { // Try nvidia-smi first (most common for encoding) cmd := exec.Command("nvidia-smi", "--query-gpu=name,driver_version", "--format=csv,noheader") output, err := cmd.Output() if err == nil { parts := strings.Split(strings.TrimSpace(string(output)), ",") if len(parts) >= 2 { model = strings.TrimSpace(parts[0]) driver = "NVIDIA " + strings.TrimSpace(parts[1]) return model, driver } } // Try lspci for any GPU cmd = exec.Command("lspci") output, err = cmd.Output() if err == nil { lines := strings.Split(string(output), "\n") for _, line := range lines { if strings.Contains(strings.ToLower(line), "vga compatible") || strings.Contains(strings.ToLower(line), "3d controller") { // Extract GPU name from lspci output parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { model = strings.TrimSpace(parts[1]) driver = "Unknown" return model, driver } } } } return "No GPU detected", "N/A" } func detectGPUWindows() (model, driver string) { // Use nvidia-smi if available (NVIDIA GPUs) cmd := exec.Command("nvidia-smi", "--query-gpu=name,driver_version", "--format=csv,noheader") utils.ApplyNoWindow(cmd) // Hide command window on Windows output, err := cmd.Output() if err == nil { parts := strings.Split(strings.TrimSpace(string(output)), ",") if len(parts) >= 2 { model = strings.TrimSpace(parts[0]) driver = "NVIDIA " + strings.TrimSpace(parts[1]) return model, driver } } // Try wmic for generic GPU info cmd = exec.Command("wmic", "path", "win32_VideoController", "get", "name,driverversion") utils.ApplyNoWindow(cmd) // Hide command window on Windows output, err = cmd.Output() if err == nil { lines := strings.Split(string(output), "\n") // Iterate through all video controllers, skip virtual/non-physical adapters for i, line := range lines { if i == 0 { // Skip header continue } line = strings.TrimSpace(line) if line == "" { continue } // Filter out virtual/software adapters lineLower := strings.ToLower(line) if strings.Contains(lineLower, "virtual") || strings.Contains(lineLower, "microsoft basic") || strings.Contains(lineLower, "remote") || strings.Contains(lineLower, "vnc") || strings.Contains(lineLower, "parsec") || strings.Contains(lineLower, "teamviewer") { logging.Debug(logging.CatSystem, "skipping virtual GPU: %s", line) continue } // Parse: Name DriverVersion // Use flexible regex to handle varying whitespace re := regexp.MustCompile(`^(.+?)\s+(\S+)$`) matches := re.FindStringSubmatch(line) if len(matches) == 3 { model = strings.TrimSpace(matches[1]) driver = strings.TrimSpace(matches[2]) logging.Debug(logging.CatSystem, "detected physical GPU: %s (driver: %s)", model, driver) return model, driver } } } return "No GPU detected", "N/A" } func detectGPUDarwin() (model, driver string) { // macOS uses system_profiler for GPU info cmd := exec.Command("system_profiler", "SPDisplaysDataType") output, err := cmd.Output() if err == nil { lines := strings.Split(string(output), "\n") for _, line := range lines { if strings.Contains(line, "Chipset Model:") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { model = strings.TrimSpace(parts[1]) } } if strings.Contains(line, "Metal:") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { driver = "Metal " + strings.TrimSpace(parts[1]) } } } } if model == "" { model = "Unknown GPU" } if driver == "" { driver = "Unknown" } return model, driver } // detectRAM returns total system RAM func detectRAM() (readable string, mb uint64) { switch runtime.GOOS { case "linux": return detectRAMLinux() case "windows": return detectRAMWindows() case "darwin": return detectRAMDarwin() default: return "Unknown", 0 } } func detectRAMLinux() (readable string, mb uint64) { cmd := exec.Command("cat", "/proc/meminfo") output, err := cmd.Output() if err != nil { logging.Debug(logging.CatSystem, "failed to read /proc/meminfo: %v", err) return "Unknown", 0 } lines := strings.Split(string(output), "\n") for _, line := range lines { if strings.HasPrefix(line, "MemTotal:") { fields := strings.Fields(line) if len(fields) >= 2 { if kb, err := strconv.ParseUint(fields[1], 10, 64); err == nil { mb = kb / 1024 gb := float64(mb) / 1024.0 readable = fmt.Sprintf("%.1f GB", gb) return readable, mb } } } } return "Unknown", 0 } func detectRAMWindows() (readable string, mb uint64) { cmd := exec.Command("wmic", "computersystem", "get", "totalphysicalmemory") utils.ApplyNoWindow(cmd) // Hide command window on Windows output, err := cmd.Output() if err != nil { logging.Debug(logging.CatSystem, "failed to run wmic computersystem: %v", err) return "Unknown", 0 } lines := strings.Split(string(output), "\n") if len(lines) >= 2 { bytesStr := strings.TrimSpace(lines[1]) if bytes, err := strconv.ParseUint(bytesStr, 10, 64); err == nil { mb = bytes / (1024 * 1024) gb := float64(mb) / 1024.0 readable = fmt.Sprintf("%.1f GB", gb) return readable, mb } } return "Unknown", 0 } func detectRAMDarwin() (readable string, mb uint64) { cmd := exec.Command("sysctl", "-n", "hw.memsize") output, err := cmd.Output() if err != nil { logging.Debug(logging.CatSystem, "failed to run sysctl hw.memsize: %v", err) return "Unknown", 0 } bytesStr := strings.TrimSpace(string(output)) if bytes, err := strconv.ParseUint(bytesStr, 10, 64); err == nil { mb = bytes / (1024 * 1024) gb := float64(mb) / 1024.0 readable = fmt.Sprintf("%.1f GB", gb) return readable, mb } return "Unknown", 0 } // Summary returns a human-readable summary of hardware info func (h HardwareInfo) Summary() string { return fmt.Sprintf("%s\n%s (%d cores @ %s)\nGPU: %s\nDriver: %s\nRAM: %s", h.OS+"/"+h.Arch, h.CPU, h.CPUCores, h.CPUMHz, h.GPU, h.GPUDriver, h.RAM, ) }