185 lines
4.8 KiB
Python
185 lines
4.8 KiB
Python
"""Cross-distribution dependency and host diagnostics."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import asdict, dataclass
|
|
from importlib.util import find_spec
|
|
import os
|
|
from pathlib import Path
|
|
import platform
|
|
import shutil
|
|
from typing import Mapping
|
|
|
|
from .launcher import find_wine_tools
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ToolStatus:
|
|
name: str
|
|
path: str | None
|
|
required: bool
|
|
purpose: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DoctorReport:
|
|
ok: bool
|
|
distribution: str
|
|
version: str
|
|
architecture: str
|
|
display_backend: str
|
|
tools: list[ToolStatus]
|
|
package_hint: str | None
|
|
warnings: list[str]
|
|
|
|
def to_dict(self) -> dict[str, object]:
|
|
return asdict(self)
|
|
|
|
|
|
def inspect_host(environment: Mapping[str, str] | None = None) -> DoctorReport:
|
|
env = os.environ if environment is None else environment
|
|
os_release = _read_os_release()
|
|
distribution = os_release.get("ID", "unknown")
|
|
version = os_release.get("VERSION_ID", "unknown")
|
|
wine_tools = find_wine_tools()
|
|
tools = [
|
|
ToolStatus(
|
|
"python3",
|
|
shutil.which("python3"),
|
|
True,
|
|
"native installer and command-line interface",
|
|
),
|
|
ToolStatus(
|
|
"python-tk",
|
|
_python_tk_path(),
|
|
True,
|
|
"native dashboard",
|
|
),
|
|
ToolStatus(
|
|
"wine",
|
|
str(wine_tools.wine) if wine_tools else None,
|
|
True,
|
|
"legacy updater support and game compatibility utilities",
|
|
),
|
|
ToolStatus(
|
|
"winetricks",
|
|
str(wine_tools.winetricks) if wine_tools else shutil.which("winetricks"),
|
|
True,
|
|
"native D3D helper DLLs for the Proton game prefix",
|
|
),
|
|
ToolStatus(
|
|
"gamescope",
|
|
shutil.which("gamescope"),
|
|
True,
|
|
"borderless X11/Wayland game container",
|
|
),
|
|
ToolStatus(
|
|
"cabextract",
|
|
shutil.which("cabextract"),
|
|
True,
|
|
"Microsoft runtime extraction through Winetricks",
|
|
),
|
|
ToolStatus(
|
|
"openssl",
|
|
shutil.which("openssl"),
|
|
True,
|
|
"signed native-launcher update verification",
|
|
),
|
|
ToolStatus(
|
|
"umu-run",
|
|
shutil.which("umu-run"),
|
|
False,
|
|
"portable Proton runtime when Steam Proton 7 is unavailable",
|
|
),
|
|
ToolStatus(
|
|
"vulkaninfo",
|
|
shutil.which("vulkaninfo"),
|
|
False,
|
|
"Vulkan diagnostics",
|
|
),
|
|
]
|
|
warnings: list[str] = []
|
|
if platform.machine().casefold() not in {"x86_64", "amd64"}:
|
|
warnings.append("PristonTale EU requires an x86-64 host with 32-bit Vulkan support.")
|
|
if not env.get("WAYLAND_DISPLAY") and not env.get("DISPLAY"):
|
|
warnings.append("No graphical X11 or Wayland session is visible.")
|
|
if shutil.which("umu-run") is None and not _has_steam_proton7(env):
|
|
warnings.append(
|
|
"No game runtime was found: install umu-launcher or Steam Proton 7.0."
|
|
)
|
|
required_ok = all(item.path is not None for item in tools if item.required)
|
|
return DoctorReport(
|
|
ok=required_ok and not any("No game runtime" in warning for warning in warnings),
|
|
distribution=distribution,
|
|
version=version,
|
|
architecture=platform.machine(),
|
|
display_backend="wayland" if env.get("WAYLAND_DISPLAY") else "x11",
|
|
tools=tools,
|
|
package_hint=_package_hint(distribution),
|
|
warnings=warnings,
|
|
)
|
|
|
|
|
|
def _read_os_release() -> dict[str, str]:
|
|
result: dict[str, str] = {}
|
|
try:
|
|
lines = Path("/etc/os-release").read_text(encoding="utf-8").splitlines()
|
|
except OSError:
|
|
return result
|
|
for line in lines:
|
|
if "=" not in line or line.startswith("#"):
|
|
continue
|
|
key, value = line.split("=", 1)
|
|
result[key] = value.strip().strip('"')
|
|
return result
|
|
|
|
|
|
def _python_tk_path() -> str | None:
|
|
try:
|
|
specification = find_spec("_tkinter")
|
|
except (ImportError, ValueError):
|
|
return None
|
|
if specification is None:
|
|
return None
|
|
return specification.origin or "available"
|
|
|
|
|
|
def _package_hint(distribution: str) -> str | None:
|
|
if distribution in {"ubuntu", "debian", "linuxmint", "pop"}:
|
|
return (
|
|
"Install Python/Tk, Wine (64- and 32-bit), Winetricks, Gamescope, "
|
|
"Cabextract, OpenSSL, and both 64- and 32-bit Vulkan drivers with APT."
|
|
)
|
|
if distribution in {"fedora", "nobara"}:
|
|
return (
|
|
"Install python3-tkinter, wine, winetricks, gamescope, cabextract, openssl, "
|
|
"and the matching 32-bit Vulkan driver with DNF."
|
|
)
|
|
if distribution in {"arch", "cachyos", "endeavouros", "manjaro"}:
|
|
return (
|
|
"Install python, tk, wine, winetricks, gamescope, cabextract, openssl, and "
|
|
"the matching lib32 Vulkan driver with Pacman."
|
|
)
|
|
if distribution in {"opensuse-tumbleweed", "opensuse-leap", "suse"}:
|
|
return (
|
|
"Install Python/Tk, Wine, Winetricks, Gamescope, Cabextract, OpenSSL, and "
|
|
"32-bit Vulkan support with Zypper."
|
|
)
|
|
return None
|
|
|
|
|
|
def _has_steam_proton7(environment: Mapping[str, str]) -> bool:
|
|
home_value = environment.get("HOME")
|
|
if not home_value:
|
|
return False
|
|
home = Path(home_value)
|
|
for steam_root in (
|
|
home / ".local/share/Steam",
|
|
home / ".steam/steam",
|
|
home / "snap/steam/common/.local/share/Steam",
|
|
home / ".var/app/com.valvesoftware.Steam/.local/share/Steam",
|
|
):
|
|
if (steam_root / "steamapps/common/Proton 7.0/proton").is_file():
|
|
return True
|
|
return False
|