0c848c41b0
When unsetting DISPLAY, Wine hangs after executing a command, and doesn't allow for other instances of wine to start, i.e. games do not launch, with a cryptic error. This also fixes the previously observed issues with `winepath.exe` and `reg.exe` never exiting. This is likely due to the newly introduced Wayland driver. This behavior can be observed when Wayland support is compiled into Wine and the wayland driver is enabled in the prefix's registry. In this case, if running under X11 and DISPLAY is not set, Wine will hang, and the process never returns.
170 lines
6.1 KiB
Python
170 lines
6.1 KiB
Python
import os
|
|
import platform
|
|
import subprocess
|
|
from configparser import ConfigParser
|
|
from logging import getLogger
|
|
from typing import Mapping, Dict, List, Tuple
|
|
|
|
from PyQt5.QtCore import QProcess, QProcessEnvironment
|
|
|
|
from rare.utils import config_helper as config
|
|
if platform.system() != "Windows":
|
|
from . import wine
|
|
if platform.system() != "Darwin":
|
|
from . import steam
|
|
|
|
logger = getLogger("CompatUtils")
|
|
|
|
|
|
# this is a copied function from legendary.utils.wine_helpers, but registry file can be specified
|
|
def read_registry(registry: str, prefix: str) -> ConfigParser:
|
|
accepted = ["system.reg", "user.reg"]
|
|
if registry not in accepted:
|
|
raise RuntimeError(f'Unknown target "{registry}" not in {accepted}')
|
|
reg = ConfigParser(comment_prefixes=(';', '#', '/', 'WINE'), allow_no_value=True,
|
|
strict=False)
|
|
reg.optionxform = str
|
|
reg.read(os.path.join(prefix, 'system.reg'))
|
|
return reg
|
|
|
|
|
|
def get_configured_qprocess(command: List[str], environment: Mapping) -> QProcess:
|
|
logger.debug("Executing command: %s", command)
|
|
proc = QProcess()
|
|
proc.setProcessChannelMode(QProcess.SeparateChannels)
|
|
penv = QProcessEnvironment()
|
|
for ek, ev in environment.items():
|
|
penv.insert(ek, ev)
|
|
proc.setProcessEnvironment(penv)
|
|
proc.setProgram(command[0])
|
|
proc.setArguments(command[1:])
|
|
return proc
|
|
|
|
|
|
def get_configured_subprocess(command: List[str], environment: Mapping) -> subprocess.Popen:
|
|
logger.debug("Executing command: %s", command)
|
|
return subprocess.Popen(
|
|
command,
|
|
stdin=None,
|
|
stdout=subprocess.PIPE,
|
|
stderr=None,
|
|
env=environment,
|
|
shell=False,
|
|
text=False,
|
|
)
|
|
|
|
|
|
def execute_subprocess(command: List[str], arguments: List[str], environment: Mapping) -> Tuple[str, str]:
|
|
proc = get_configured_subprocess(command + arguments, environment)
|
|
print(proc.args)
|
|
out, err = proc.communicate()
|
|
out, err = out.decode("utf-8", "ignore") if out else "", err.decode("utf-8", "ignore") if err else ""
|
|
|
|
# lk: the following is a work-around for wineserver sometimes hanging around after
|
|
proc = get_configured_subprocess(command + ["wineboot", "-e"], environment)
|
|
_, _ = proc.communicate()
|
|
return out, err
|
|
|
|
|
|
def execute_qprocess(command: List[str], arguments: List[str], environment: Mapping) -> Tuple[str, str]:
|
|
proc = get_configured_qprocess(command + arguments, environment)
|
|
proc.start()
|
|
proc.waitForFinished(-1)
|
|
out, err = (
|
|
proc.readAllStandardOutput().data().decode("utf-8", "ignore"),
|
|
proc.readAllStandardError().data().decode("utf-8", "ignore")
|
|
)
|
|
proc.deleteLater()
|
|
|
|
# lk: the following is a work-around for wineserver sometimes hanging around after
|
|
proc = get_configured_qprocess(command + ["wineboot", "-e"], environment)
|
|
proc.start()
|
|
proc.waitForFinished(-1)
|
|
proc.deleteLater()
|
|
|
|
return out, err
|
|
|
|
|
|
def execute(command: List[str], arguments: List[str], environment: Mapping) -> Tuple[str, str]:
|
|
# Use the current environment if we are in flatpak or our own if we are on host
|
|
# In flatpak our environment is passed through `flatpak-spawn` arguments
|
|
if os.environ.get("container") == "flatpak":
|
|
flatpak_command = ["flatpak-spawn", "--host"]
|
|
for name, value in environment.items():
|
|
flatpak_command.append(f"--env={name}={value}")
|
|
_command = flatpak_command + command
|
|
_environment = os.environ.copy()
|
|
else:
|
|
_command = command
|
|
_environment = environment
|
|
|
|
try:
|
|
out, err = execute_qprocess(_command, arguments, _environment)
|
|
except Exception as e:
|
|
out, err = "", str(e)
|
|
|
|
return out, err
|
|
|
|
|
|
def resolve_path(command: List[str], environment: Mapping, path: str) -> str:
|
|
path = path.strip().replace("/", "\\")
|
|
# lk: if path does not exist form
|
|
arguments = ["cmd.exe", "/c", "echo", path]
|
|
# lk: if path exists and needs a case-sensitive interpretation form
|
|
# cmd = [wine_cmd, 'cmd', '/c', f'cd {path} & cd']
|
|
out, err = execute(command, arguments, environment)
|
|
out, err = out.strip(), err.strip()
|
|
if not out:
|
|
logger.error("Failed to resolve wine path due to \"%s\"", err)
|
|
return out
|
|
return out.strip('"')
|
|
|
|
|
|
def query_reg_path(wine_exec: str, wine_env: Mapping, reg_path: str):
|
|
raise NotImplementedError
|
|
|
|
|
|
def query_reg_key(command: List[str], environment: Mapping, reg_path: str, reg_key) -> str:
|
|
arguments = ["reg.exe", "query", reg_path, "/v", reg_key]
|
|
out, err = execute(command, arguments, environment)
|
|
out, err = out.strip(), err.strip()
|
|
if not out:
|
|
logger.error("Failed to query registry key due to \"%s\"", err)
|
|
return out
|
|
lines = out.split("\n")
|
|
keys: Dict = {}
|
|
for line in lines:
|
|
if line.startswith(" "*4):
|
|
key = [x for x in line.split(" "*4, 3) if bool(x)]
|
|
keys.update({key[0]: key[2]})
|
|
return keys.get(reg_key, "")
|
|
|
|
|
|
def convert_to_windows_path(wine_exec: str, wine_env: Mapping, path: str) -> str:
|
|
raise NotImplementedError
|
|
|
|
|
|
def convert_to_unix_path(command: List[str], environment: Mapping, path: str) -> str:
|
|
path = path.strip().strip('"')
|
|
arguments = ["winepath.exe", "-u", path]
|
|
out, err = execute(command, arguments, environment)
|
|
out, err = out.strip(), err.strip()
|
|
if not out:
|
|
logger.error("Failed to convert to unix path due to \"%s\"", err)
|
|
return os.path.realpath(out) if (out := out.strip()) else out
|
|
|
|
|
|
def get_host_environment(app_environment: Dict, silent: bool = True) -> Dict:
|
|
# Get a clean environment if we are in flatpak, this environment will be passed
|
|
# to `flatpak-spawn`, otherwise use the system's.
|
|
_environ = {} if os.environ.get("container") == "flatpak" else os.environ.copy()
|
|
_environ.update(app_environment)
|
|
if silent:
|
|
_environ["WINEESYNC"] = "0"
|
|
_environ["WINEFSYNC"] = "0"
|
|
_environ["WINE_DISABLE_FAST_SYNC"] = "1"
|
|
_environ["WINEDEBUG"] = "-all"
|
|
_environ["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;"
|
|
# lk: pressure-vessel complains about this but it doesn't fail due to it
|
|
#_environ["DISPLAY"] = ""
|
|
return _environ
|