From e51508017111f0c05d2c9c11820c26dc1699ab96 Mon Sep 17 00:00:00 2001 From: m4ko Date: Thu, 27 Aug 2026 22:02:31 +0200 Subject: [PATCH] Added fallbacks for fsync issues on CachyOS. --- ptlinux/__init__.py | 2 +- ptlinux/cli.py | 6 +++++ ptlinux/runtime.py | 64 +++++++++++++++++++++++++++++++++++++++++++-- ptlinux/settings.py | 4 +++ ptlinux/ui.py | 10 +++++++ 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/ptlinux/__init__.py b/ptlinux/__init__.py index 693a43d..6c48bce 100644 --- a/ptlinux/__init__.py +++ b/ptlinux/__init__.py @@ -4,4 +4,4 @@ from .paths import AppPaths __all__ = ["AppPaths", "__version__"] -__version__ = "0.5.2" +__version__ = "0.5.3" diff --git a/ptlinux/cli.py b/ptlinux/cli.py index 4119670..ee3b197 100644 --- a/ptlinux/cli.py +++ b/ptlinux/cli.py @@ -300,6 +300,7 @@ def _login_diagnostics(paths: AppPaths, timeout_seconds: float, as_json: bool) - probe = report.login_server print(f"Launcher: {report.launcher_version} ({report.launcher_module})") print(f"Python: {report.python_executable}") + print(f"Host: {report.distribution_id} ({report.kernel_release})") status = "REACHABLE" if probe.reachable else "UNREACHABLE" latency = f", {probe.latency_ms:.1f} ms" if probe.latency_ms is not None else "" print(f"Login server: {status} ({probe.host}:{probe.port}{latency})") @@ -317,6 +318,11 @@ def _login_diagnostics(paths: AppPaths, timeout_seconds: float, as_json: bool) - print(f"Runtime: {report.runtime_kind} ({report.runtime_path or 'unknown path'})") else: print(f"Runtime: unavailable ({report.runtime_error or 'selection failed'})") + if report.proton_sync_effective is not None: + print( + "Proton sync: " + f"{report.proton_sync_configured} -> {report.proton_sync_effective}" + ) prefix_status = "ready" if report.prefix_ready else "not ready" directx_status = "ready" if report.directx_ready else "not ready" print(f"Prefix: {prefix_status}; DirectX helpers {directx_status}") diff --git a/ptlinux/runtime.py b/ptlinux/runtime.py index 76694a2..0ab16fd 100644 --- a/ptlinux/runtime.py +++ b/ptlinux/runtime.py @@ -7,6 +7,7 @@ from datetime import datetime import json import os from pathlib import Path +import platform import shutil import sys from typing import Mapping @@ -25,6 +26,8 @@ WINETRICKS_CODEPAGE_PROBE = ( 'winetricks_early_wine_arch cmd.exe /c "chcp 65001 > nul & echo %$1%"' ) WINETRICKS_PROTON7_PROBE = 'winetricks_early_wine_arch cmd.exe /c "echo %$1%"' +RUNTIME_DEFAULT_SYNC = "runtime-default" +CACHYOS_MARKER = "cachyos" class RuntimeErrorPT(RuntimeError): @@ -238,7 +241,11 @@ def run_game( WINDOWS_GAME_EXE, "PTEU_REBORN", ] - environment = _proton_environment(paths, selection) + environment = _proton_environment( + paths, + selection, + synchronization=resolve_proton_sync_mode(settings.proton_sync), + ) if use_gamebox: game_command = build_gamebox_command(game_command, settings) environment["PYTHONPATH"] = _runtime_python_path( @@ -274,10 +281,12 @@ def _umu_environment(paths: AppPaths) -> dict[str, str]: def _proton_environment( paths: AppPaths, selection: RuntimeSelection, + *, + synchronization: str = RUNTIME_DEFAULT_SYNC, ) -> dict[str, str]: if not selection.steam_root: raise RuntimeErrorPT("Steam root is missing for Proton") - return { + environment = { "STEAM_COMPAT_CLIENT_INSTALL_PATH": selection.steam_root, "STEAM_COMPAT_DATA_PATH": str(paths.compat_data), "STEAM_COMPAT_INSTALL_PATH": str(paths.runtime_view), @@ -288,6 +297,57 @@ def _proton_environment( "PROTON_LOG": "0", "DXVK_STATE_CACHE_PATH": str(paths.dxvk_cache), } + environment.update(_proton_sync_environment(synchronization)) + return environment + + +def detect_distribution_id() -> str: + """Return the freedesktop distribution ID without invoking distro tools.""" + + try: + value = platform.freedesktop_os_release().get("ID", "") + except OSError: + return "" + return value.strip().casefold() + + +def resolve_proton_sync_mode( + configured: str, + *, + distribution_id: str | None = None, + kernel_release: str | None = None, +) -> str: + """Resolve the configured mode, applying the narrow CachyOS compatibility fallback.""" + + if configured not in {"auto", "fsync", "esync"}: + raise ValueError(f"unsupported Proton synchronization mode: {configured}") + if configured != "auto": + return configured + distribution = detect_distribution_id() if distribution_id is None else distribution_id + kernel = platform.release() if kernel_release is None else kernel_release + if CACHYOS_MARKER in distribution.casefold() or CACHYOS_MARKER in kernel.casefold(): + return "esync" + return RUNTIME_DEFAULT_SYNC + + +def _proton_sync_environment(synchronization: str) -> dict[str, str]: + if synchronization == RUNTIME_DEFAULT_SYNC: + return {} + if synchronization == "esync": + return { + "PROTON_NO_FSYNC": "1", + "WINEFSYNC": "0", + "PROTON_NO_ESYNC": "0", + "WINEESYNC": "1", + } + if synchronization == "fsync": + return { + "PROTON_NO_FSYNC": "0", + "WINEFSYNC": "1", + "PROTON_NO_ESYNC": "0", + "WINEESYNC": "1", + } + raise ValueError(f"unsupported resolved Proton synchronization mode: {synchronization}") def _ensure_proton_game_link(paths: AppPaths) -> None: diff --git a/ptlinux/settings.py b/ptlinux/settings.py index bf8a75c..ff7135b 100644 --- a/ptlinux/settings.py +++ b/ptlinux/settings.py @@ -22,6 +22,7 @@ class Settings: scaler: str = "fit" filter: str = "linear" runtime: str = "auto" + proton_sync: str = "auto" proton_path: str | None = None game_source: str | None = None @@ -50,6 +51,7 @@ def load_settings(paths: AppPaths) -> Settings: scaler=_string(value, "scaler", "fit"), filter=_string(value, "filter", "linear"), runtime=_string(value, "runtime", "auto"), + proton_sync=_string(value, "proton_sync", "auto"), proton_path=_optional_string(value, "proton_path"), game_source=_optional_string(value, "game_source"), ) @@ -95,6 +97,8 @@ def _validate(settings: Settings) -> None: raise ValueError(f"unsupported Gamescope filter: {settings.filter}") if settings.runtime not in {"auto", "proton7", "umu"}: raise ValueError(f"unsupported runtime selection: {settings.runtime}") + if settings.proton_sync not in {"auto", "fsync", "esync"}: + raise ValueError(f"unsupported Proton synchronization mode: {settings.proton_sync}") def _integer(value: dict[str, object], key: str, default: int) -> int: diff --git a/ptlinux/ui.py b/ptlinux/ui.py index 09b4a71..ee3ed40 100644 --- a/ptlinux/ui.py +++ b/ptlinux/ui.py @@ -1371,6 +1371,7 @@ def run_dashboard(paths: AppPaths) -> None: scaler_value = tk.StringVar(value=current_settings.scaler) filter_value = tk.StringVar(value=current_settings.filter) runtime_value = tk.StringVar(value=current_settings.runtime) + proton_sync_value = tk.StringVar(value=current_settings.proton_sync) proton_path_value = tk.StringVar(value=current_settings.proton_path or "") game_source_value = tk.StringVar(value=current_settings.game_source or "") @@ -1511,6 +1512,7 @@ def run_dashboard(paths: AppPaths) -> None: scaler=scaler_value.get(), filter=filter_value.get(), runtime=runtime_value.get(), + proton_sync=proton_sync_value.get(), proton_path=proton_path_value.get().strip() or None, game_source=game_source_value.get().strip() or None, ) @@ -1637,6 +1639,14 @@ def run_dashboard(paths: AppPaths) -> None: runtime_value, ("auto", "proton7", "umu"), ) + add_choice( + rendering_card, + 8, + "Proton synchronization", + "Auto uses esync on CachyOS", + proton_sync_value, + ("auto", "fsync", "esync"), + ) overrides_card = ttk.LabelFrame( content,