339 lines
7.8 KiB
Python
339 lines
7.8 KiB
Python
import os
|
|
from dataclasses import dataclass
|
|
from enum import Enum, StrEnum
|
|
from functools import cached_property
|
|
from pathlib import Path
|
|
|
|
from archinstall.lib.command import SysCommand
|
|
from archinstall.lib.exceptions import SysCallError
|
|
from archinstall.lib.log import debug
|
|
from archinstall.lib.networking import enrich_iface_types, list_interfaces
|
|
from archinstall.lib.translationhandler import tr
|
|
|
|
|
|
class CPUVendor(StrEnum):
|
|
AMD = 'AuthenticAMD'
|
|
INTEL = 'GenuineIntel'
|
|
_UNKNOWN = 'unknown'
|
|
|
|
def _has_microcode(self) -> bool:
|
|
match self:
|
|
case CPUVendor.AMD | CPUVendor.INTEL:
|
|
return True
|
|
case _:
|
|
return False
|
|
|
|
def get_ucode(self) -> Path | None:
|
|
if self._has_microcode():
|
|
return Path(self.name.lower() + '-ucode.img')
|
|
return None
|
|
|
|
|
|
class GfxPackage(Enum):
|
|
Dkms = 'dkms'
|
|
IntelMediaDriver = 'intel-media-driver'
|
|
LibvaIntelDriver = 'libva-intel-driver'
|
|
VplGpuRt = 'vpl-gpu-rt'
|
|
LibvaNvidiaDriver = 'libva-nvidia-driver'
|
|
Mesa = 'mesa'
|
|
NvidiaOpen = 'nvidia-open'
|
|
NvidiaOpenDkms = 'nvidia-open-dkms'
|
|
VulkanIntel = 'vulkan-intel'
|
|
VulkanRadeon = 'vulkan-radeon'
|
|
VulkanNouveau = 'vulkan-nouveau'
|
|
Xf86VideoAmdgpu = 'xf86-video-amdgpu'
|
|
Xf86VideoAti = 'xf86-video-ati'
|
|
Xf86VideoNouveau = 'xf86-video-nouveau'
|
|
|
|
|
|
class GfxDriver(Enum):
|
|
AllOpenSource = 'All open-source'
|
|
AmdOpenSource = 'AMD / ATI (open-source)'
|
|
IntelOpenSource = 'Intel (open-source)'
|
|
NvidiaOpenKernel = 'Nvidia (open kernel module for newer GPUs, Turing+)'
|
|
NvidiaOpenSource = 'Nvidia (open-source nouveau driver)'
|
|
VMOpenSource = 'VirtualBox (open-source)'
|
|
|
|
def is_nvidia(self) -> bool:
|
|
match self:
|
|
case GfxDriver.NvidiaOpenSource | GfxDriver.NvidiaOpenKernel:
|
|
return True
|
|
case _:
|
|
return False
|
|
|
|
def is_nvidia_proprietary(self) -> bool:
|
|
"""
|
|
True for Nvidia drivers that ship proprietary userspace components.
|
|
Currently only NvidiaOpenKernel (nvidia-open-dkms): open kernel module
|
|
paired with proprietary userspace. NvidiaOpenSource (nouveau) is fully
|
|
open and works with Sway, so it is excluded.
|
|
"""
|
|
match self:
|
|
case GfxDriver.NvidiaOpenKernel:
|
|
return True
|
|
case _:
|
|
return False
|
|
|
|
def packages_text(self) -> str:
|
|
pkg_names = [p.value for p in self.gfx_packages()]
|
|
text = tr('Installed packages') + ':\n'
|
|
|
|
for p in sorted(pkg_names):
|
|
text += f' - {p}\n'
|
|
|
|
return text
|
|
|
|
def gfx_packages(self) -> list[GfxPackage]:
|
|
packages: list[GfxPackage] = []
|
|
|
|
match self:
|
|
case GfxDriver.AllOpenSource:
|
|
packages += [
|
|
GfxPackage.Mesa,
|
|
GfxPackage.Xf86VideoAmdgpu,
|
|
GfxPackage.Xf86VideoAti,
|
|
GfxPackage.Xf86VideoNouveau,
|
|
GfxPackage.LibvaIntelDriver,
|
|
GfxPackage.IntelMediaDriver,
|
|
GfxPackage.VplGpuRt,
|
|
GfxPackage.VulkanRadeon,
|
|
GfxPackage.VulkanIntel,
|
|
GfxPackage.VulkanNouveau,
|
|
]
|
|
case GfxDriver.AmdOpenSource:
|
|
packages += [
|
|
GfxPackage.Mesa,
|
|
GfxPackage.Xf86VideoAmdgpu,
|
|
GfxPackage.Xf86VideoAti,
|
|
GfxPackage.VulkanRadeon,
|
|
]
|
|
case GfxDriver.IntelOpenSource:
|
|
packages += [
|
|
GfxPackage.Mesa,
|
|
GfxPackage.LibvaIntelDriver,
|
|
GfxPackage.IntelMediaDriver,
|
|
GfxPackage.VplGpuRt,
|
|
GfxPackage.VulkanIntel,
|
|
]
|
|
case GfxDriver.NvidiaOpenKernel:
|
|
packages += [
|
|
GfxPackage.NvidiaOpenDkms,
|
|
GfxPackage.Dkms,
|
|
GfxPackage.LibvaNvidiaDriver,
|
|
]
|
|
case GfxDriver.NvidiaOpenSource:
|
|
packages += [
|
|
GfxPackage.Mesa,
|
|
GfxPackage.Xf86VideoNouveau,
|
|
GfxPackage.VulkanNouveau,
|
|
]
|
|
case GfxDriver.VMOpenSource:
|
|
packages += [
|
|
GfxPackage.Mesa,
|
|
]
|
|
|
|
return packages
|
|
|
|
|
|
class _SysInfo:
|
|
def __init__(self) -> None:
|
|
pass
|
|
|
|
@cached_property
|
|
def has_battery(self) -> bool:
|
|
for type_path in Path('/sys/class/power_supply/').glob('*/type'):
|
|
try:
|
|
with open(type_path) as f:
|
|
if f.read().strip() == 'Battery':
|
|
return True
|
|
except OSError:
|
|
continue
|
|
|
|
return False
|
|
|
|
@cached_property
|
|
def cpu_info(self) -> dict[str, str]:
|
|
"""
|
|
Returns system cpu information
|
|
"""
|
|
cpu_info_path = Path('/proc/cpuinfo')
|
|
cpu: dict[str, str] = {}
|
|
|
|
with cpu_info_path.open() as file:
|
|
for line in file:
|
|
if line := line.strip():
|
|
key, value = line.split(':', maxsplit=1)
|
|
cpu[key.strip()] = value.strip()
|
|
|
|
return cpu
|
|
|
|
@cached_property
|
|
def loaded_modules(self) -> list[str]:
|
|
"""
|
|
Returns loaded kernel modules
|
|
"""
|
|
modules_path = Path('/proc/modules')
|
|
modules: list[str] = []
|
|
|
|
with modules_path.open() as file:
|
|
for line in file:
|
|
module = line.split(maxsplit=1)[0]
|
|
modules.append(module)
|
|
|
|
return modules
|
|
|
|
@cached_property
|
|
def graphics_devices(self) -> dict[str, str]:
|
|
"""
|
|
Returns detected graphics devices (cached)
|
|
"""
|
|
cards: dict[str, str] = {}
|
|
for line in SysCommand('lspci'):
|
|
if b' VGA ' in line or b' 3D ' in line:
|
|
_, identifier = line.split(b': ', 1)
|
|
cards[identifier.strip().decode('UTF-8')] = str(line)
|
|
return cards
|
|
|
|
|
|
_sys_info = _SysInfo()
|
|
|
|
|
|
class SysInfo:
|
|
@staticmethod
|
|
def has_battery() -> bool:
|
|
return _sys_info.has_battery
|
|
|
|
@staticmethod
|
|
def has_wifi() -> bool:
|
|
ifaces = list(list_interfaces().values())
|
|
return 'WIRELESS' in enrich_iface_types(ifaces).values()
|
|
|
|
@staticmethod
|
|
def has_uefi() -> bool:
|
|
return os.path.isdir('/sys/firmware/efi')
|
|
|
|
@staticmethod
|
|
def _graphics_devices() -> dict[str, str]:
|
|
return _sys_info.graphics_devices
|
|
|
|
@staticmethod
|
|
def has_nvidia_graphics() -> bool:
|
|
return any('nvidia' in x.lower() for x in _sys_info.graphics_devices)
|
|
|
|
@staticmethod
|
|
def has_amd_graphics() -> bool:
|
|
return any('amd' in x.lower() for x in _sys_info.graphics_devices)
|
|
|
|
@staticmethod
|
|
def has_intel_graphics() -> bool:
|
|
return any('intel' in x.lower() for x in _sys_info.graphics_devices)
|
|
|
|
@staticmethod
|
|
def cpu_vendor() -> CPUVendor | None:
|
|
if vendor := _sys_info.cpu_info.get('vendor_id'):
|
|
try:
|
|
return CPUVendor(vendor)
|
|
except ValueError:
|
|
debug(f"Unknown CPU vendor '{vendor}' detected.")
|
|
return CPUVendor._UNKNOWN
|
|
return None
|
|
|
|
@staticmethod
|
|
def cpu_model() -> str | None:
|
|
return _sys_info.cpu_info.get('model name', None)
|
|
|
|
@staticmethod
|
|
def sys_vendor() -> str | None:
|
|
try:
|
|
with open('/sys/devices/virtual/dmi/id/sys_vendor') as vendor:
|
|
return vendor.read().strip()
|
|
except FileNotFoundError:
|
|
return None
|
|
|
|
@staticmethod
|
|
def product_name() -> str | None:
|
|
try:
|
|
with open('/sys/devices/virtual/dmi/id/product_name') as product:
|
|
return product.read().strip()
|
|
except FileNotFoundError:
|
|
return None
|
|
|
|
@staticmethod
|
|
def virtualization() -> str | None:
|
|
try:
|
|
return str(SysCommand('systemd-detect-virt')).strip('\r\n')
|
|
except SysCallError as err:
|
|
debug(f'Could not detect virtual system: {err}')
|
|
|
|
return None
|
|
|
|
@staticmethod
|
|
def is_vm() -> bool:
|
|
try:
|
|
result = SysCommand('systemd-detect-virt')
|
|
return b'none' not in b''.join(result).lower()
|
|
except SysCallError as err:
|
|
debug(f'System is not running in a VM: {err}')
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def requires_sof_fw() -> bool:
|
|
return 'snd_sof' in _sys_info.loaded_modules
|
|
|
|
@staticmethod
|
|
def requires_alsa_fw() -> bool:
|
|
modules = (
|
|
'snd_asihpi',
|
|
'snd_cs46xx',
|
|
'snd_darla20',
|
|
'snd_darla24',
|
|
'snd_echo3g',
|
|
'snd_emu10k1',
|
|
'snd_gina20',
|
|
'snd_gina24',
|
|
'snd_hda_codec_ca0132',
|
|
'snd_hdsp',
|
|
'snd_indigo',
|
|
'snd_indigodj',
|
|
'snd_indigodjx',
|
|
'snd_indigoio',
|
|
'snd_indigoiox',
|
|
'snd_layla20',
|
|
'snd_layla24',
|
|
'snd_mia',
|
|
'snd_mixart',
|
|
'snd_mona',
|
|
'snd_pcxhr',
|
|
'snd_vx_lib',
|
|
)
|
|
|
|
for loaded_module in _sys_info.loaded_modules:
|
|
if loaded_module in modules:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MemInfo:
|
|
mem_total: int
|
|
mem_free: int
|
|
mem_available: int
|
|
|
|
|
|
def read_meminfo() -> MemInfo:
|
|
data: dict[str, int] = {}
|
|
|
|
with Path('/proc/meminfo').open() as file:
|
|
for line in file:
|
|
key, _, remainder = line.partition(':')
|
|
num, _, _ = remainder.strip().partition(' ')
|
|
data[key] = int(num)
|
|
|
|
return MemInfo(
|
|
mem_total=data['MemTotal'],
|
|
mem_free=data['MemFree'],
|
|
mem_available=data['MemAvailable'],
|
|
)
|