177 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
| # linux_hardware_info.py
 | |
| import subprocess
 | |
| import re
 | |
| import os # For listing /proc/asound
 | |
| import glob # For wildcard matching in /proc/asound
 | |
| 
 | |
| def _run_command(command: list[str], check_stderr_for_error=False) -> tuple[str, str, int]:
 | |
|     """
 | |
|     Helper to run a command and return its stdout, stderr, and return code.
 | |
|     Args:
 | |
|         check_stderr_for_error: If True, treat any output on stderr as an error condition for return code.
 | |
|     Returns:
 | |
|         (stdout, stderr, return_code)
 | |
|     """
 | |
|     try:
 | |
|         process = subprocess.run(command, capture_output=True, text=True, check=False) # check=False to handle errors manually
 | |
| 
 | |
|         # Some tools (like lspci without -k if no driver) might return 0 but print to stderr.
 | |
|         # However, for most tools here, a non-zero return code is the primary error indicator.
 | |
|         # If check_stderr_for_error is True and stderr has content, consider it an error for simplicity here.
 | |
|         # effective_return_code = process.returncode
 | |
|         # if check_stderr_for_error and process.stderr and process.returncode == 0:
 | |
|         #     effective_return_code = 1 # Treat as error
 | |
| 
 | |
|         return process.stdout, process.stderr, process.returncode
 | |
|     except FileNotFoundError:
 | |
|         print(f"Error: Command '{command[0]}' not found.")
 | |
|         return "", f"Command not found: {command[0]}", 127 # Standard exit code for command not found
 | |
|     except Exception as e:
 | |
|         print(f"An unexpected error occurred with command {' '.join(command)}: {e}")
 | |
|         return "", str(e), 1
 | |
| 
 | |
| 
 | |
| def get_pci_devices_info() -> list[dict]:
 | |
|     """
 | |
|     Gets a list of dictionaries, each containing info about a PCI device,
 | |
|     focusing on VGA, Audio, and Ethernet controllers using lspci.
 | |
|     """
 | |
|     stdout, stderr, return_code = _run_command(["lspci", "-nnk"])
 | |
|     if return_code != 0 or not stdout:
 | |
|         print(f"lspci command failed or produced no output. stderr: {stderr}")
 | |
|         return []
 | |
| 
 | |
|     devices = []
 | |
|     regex = re.compile(
 | |
|         r"^[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.\d\s+"
 | |
|         r"(.+?)\s+"
 | |
|         r"\[([0-9a-fA-F]{4})\]:\s+" # Class Code in hex, like 0300 for VGA
 | |
|         r"(.+?)\s+"
 | |
|         r"\[([0-9a-fA-F]{4}):([0-9a-fA-F]{4})\]"  # Vendor and Device ID
 | |
|     )
 | |
| 
 | |
|     for line in stdout.splitlines():
 | |
|         match = regex.search(line)
 | |
|         if match:
 | |
|             class_desc = match.group(1).strip()
 | |
|             # class_code = match.group(2).strip() # Not directly used yet but captured
 | |
|             full_desc = match.group(3).strip()
 | |
|             vendor_id = match.group(4).lower()
 | |
|             device_id = match.group(5).lower()
 | |
| 
 | |
|             device_type = None
 | |
|             if "VGA compatible controller" in class_desc or "3D controller" in class_desc:
 | |
|                 device_type = "VGA"
 | |
|             elif "Audio device" in class_desc:
 | |
|                 device_type = "Audio"
 | |
|             elif "Ethernet controller" in class_desc:
 | |
|                 device_type = "Ethernet"
 | |
|             elif "Network controller" in class_desc:
 | |
|                 device_type = "Network (Wi-Fi?)"
 | |
| 
 | |
|             if device_type:
 | |
|                 cleaned_desc = full_desc
 | |
|                 # Simple cleanup attempts (can be expanded)
 | |
|                 vendors_to_strip = ["Intel Corporation", "NVIDIA Corporation", "Advanced Micro Devices, Inc. [AMD/ATI]", "AMD [ATI]", "Realtek Semiconductor Co., Ltd."]
 | |
|                 for v_strip in vendors_to_strip:
 | |
|                     if cleaned_desc.startswith(v_strip):
 | |
|                         cleaned_desc = cleaned_desc[len(v_strip):].strip()
 | |
|                         break
 | |
|                 # Remove revision if present at end, e.g. (rev 31)
 | |
|                 cleaned_desc = re.sub(r'\s*\(rev [0-9a-fA-F]{2}\)$', '', cleaned_desc)
 | |
| 
 | |
| 
 | |
|                 devices.append({
 | |
|                     "type": device_type,
 | |
|                     "vendor_id": vendor_id,
 | |
|                     "device_id": device_id,
 | |
|                     "description": cleaned_desc.strip() if cleaned_desc else full_desc, # Fallback to full_desc
 | |
|                     "full_lspci_line": line.strip()
 | |
|                 })
 | |
|     return devices
 | |
| 
 | |
| def get_cpu_info() -> dict:
 | |
|     """
 | |
|     Gets CPU information using lscpu.
 | |
|     """
 | |
|     stdout, stderr, return_code = _run_command(["lscpu"])
 | |
|     if return_code != 0 or not stdout:
 | |
|         print(f"lscpu command failed or produced no output. stderr: {stderr}")
 | |
|         return {}
 | |
| 
 | |
|     info = {}
 | |
|     regex = re.compile(r"^(CPU family|Model name|Vendor ID|Model|Stepping|Flags|Architecture):\s+(.*)$")
 | |
|     for line in stdout.splitlines():
 | |
|         match = regex.match(line)
 | |
|         if match:
 | |
|             key = match.group(1).strip()
 | |
|             value = match.group(2).strip()
 | |
|             info[key] = value
 | |
|     return info
 | |
| 
 | |
| def get_audio_codecs() -> list[str]:
 | |
|     """
 | |
|     Detects audio codec names by parsing /proc/asound/card*/codec#*.
 | |
|     Returns a list of unique codec name strings.
 | |
|     E.g., ["Realtek ALC897", "Intel Kaby Lake HDMI"]
 | |
|     """
 | |
|     codec_files = glob.glob("/proc/asound/card*/codec#*")
 | |
|     if not codec_files:
 | |
|         # Fallback for systems where codec#* might not exist, try card*/id
 | |
|         codec_files = glob.glob("/proc/asound/card*/id")
 | |
| 
 | |
|     codecs = set() # Use a set to store unique codec names
 | |
| 
 | |
|     for codec_file_path in codec_files:
 | |
|         try:
 | |
|             with open(codec_file_path, 'r') as f:
 | |
|                 content = f.read()
 | |
|                 # For codec#* files
 | |
|                 codec_match = re.search(r"Codec:\s*(.*)", content)
 | |
|                 if codec_match:
 | |
|                     codecs.add(codec_match.group(1).strip())
 | |
| 
 | |
|                 # For card*/id files (often just the card name, but sometimes hints at codec)
 | |
|                 # This is a weaker source but a fallback.
 | |
|                 if "/id" in codec_file_path and not codec_match: # Only if no "Codec:" line found
 | |
|                      # The content of /id is usually the card name, e.g. "HDA Intel PCH"
 | |
|                      # This might not be the specific codec chip but can be a hint.
 | |
|                      # For now, let's only add if it seems like a specific codec name.
 | |
|                      # This part needs more refinement if used as a primary source.
 | |
|                      # For now, we prioritize "Codec: " lines.
 | |
|                      if "ALC" in content or "CS" in content or "AD" in content: # Common codec prefixes
 | |
|                          codecs.add(content.strip())
 | |
| 
 | |
| 
 | |
|         except Exception as e:
 | |
|             print(f"Error reading or parsing codec file {codec_file_path}: {e}")
 | |
| 
 | |
|     if not codecs and not codec_files: # If no files found at all
 | |
|         print("No /proc/asound/card*/codec#* or /proc/asound/card*/id files found. Cannot detect audio codecs this way.")
 | |
| 
 | |
|     return sorted(list(codecs))
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     print("--- CPU Info ---")
 | |
|     cpu_info = get_cpu_info()
 | |
|     if cpu_info:
 | |
|         for key, value in cpu_info.items():
 | |
|             print(f"  {key}: {value}")
 | |
|     else: print("  Could not retrieve CPU info.")
 | |
| 
 | |
|     print("\n--- PCI Devices ---")
 | |
|     pci_devs = get_pci_devices_info()
 | |
|     if pci_devs:
 | |
|         for dev in pci_devs:
 | |
|             print(f"  Type: {dev['type']}, Vendor: {dev['vendor_id']}, Device: {dev['device_id']}, Desc: {dev['description']}")
 | |
|     else: print("  No relevant PCI devices found or lspci not available.")
 | |
| 
 | |
|     print("\n--- Audio Codecs ---")
 | |
|     audio_codecs = get_audio_codecs()
 | |
|     if audio_codecs:
 | |
|         for codec in audio_codecs:
 | |
|             print(f"  Detected Codec: {codec}")
 | |
|     else:
 | |
|         print("  No specific audio codecs detected via /proc/asound.")
 |