Skip to content

Commit

Permalink
Add functionality to use winetricks (#109)
Browse files Browse the repository at this point in the history
* umu_proton: add proton class

* umu_util: add winetricks functionality

* umu_run: add checks when using winetricks

* umu_run: set environment variables for winetricks

* umu_run: config winetricks to run unattended

* umu_run: change directory when using winetricks

* umu_run: fix exception when passing no winetricks verbs

* umu_run: set WINEDLLPATH

* umu_run: fix exit 3 status code for winetricks

* umu_run: delete winetricks and protonfix paths

* umu_run: update comments

* umu_util: add missing type

* umu_run: fix not setting EXE and STEAM_COMPAT_INSTALL_PATH

- Without these variables set, winetricks will be missing and will result in a 'ShellExecuteEx failed: File not found' error.

* umu_util: allow processing winetricks verbs in bulk

- Allows passing verbs separated by spaces to apply verbs in bulk to the prefix

* umu_run: update logic when swapping option and verb

* Ruff lint

* umu_run: respect order of winetricks verbs

* umu_run: add winetricks as positional argument

* umu_run: prefer surrounding verb with quotes

* umu_run: update comment

* umu_run: prefer assigning a new list than list.insert

* umu_util: fix bug when parsing verbs

* umu_util: update logic

* umu_util: rename function

* umu_util: add checks for is_installed_verb

* umu_test: add test for is_installed_verb

* umu_test: add test for is_winetricks_verb

* umu_test: add tests when passing winetricks as positional argument

* docs: add example of installing winetricks verbs

* umu_util: fix logic when checking winetricks verbs

- It doesn't make sense to check if the value in the log file is a verb and not the input.

* umu_util: refactor is_winetricks_verb to log messages

* umu_run: remove error messages for winetricks

* umu_util: always process in bulk

* umu_util: always pass verbs as a list

* umu_test: update tests

* Ruff lint

* umu_util: fix AttributeError when checking winetricks verbs

* umu_test: update tests

* docs: update example
  • Loading branch information
R1kaB3rN authored Jun 7, 2024
1 parent 4618ded commit f4fb158
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 6 deletions.
6 changes: 6 additions & 0 deletions docs/umu.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ $ WINEPREFIX=~/.wine GAMEID=0 PROTONPATH=GE-Proton9-1 umu-run ~/foo.exe
$ WINEPREFIX=~/.wine GAMEID=0 PROTONPATH=GE-Proton umu-run ~/foo.exe
```

*Example 11. Run winetricks verbs*

```
$ GAMEID=0 PROTONPATH=GE-Proton umu-run winetricks quartz wmp11 qasf
```

# SEE ALSO

_umu_(5)
Expand Down
18 changes: 18 additions & 0 deletions umu/umu_proton.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@
tar_filter: Callable[[str, str], TarInfo] = None


class Proton:
"""Model paths to relevant files and directories for Proton."""

def __init__(self, base_dir: str) -> None: # noqa: D107
self.base_dir = base_dir + "/"
self.dist_dir = self.path("files/")
self.bin_dir = self.path("files/bin/")
self.lib_dir = self.path("files/lib/")
self.lib64_dir = self.path("files/lib64/")
self.version_file = self.path("version")
self.wine_bin = self.bin_dir + "wine"
self.wine64_bin = self.bin_dir + "wine64"
self.wineserver_bin = self.bin_dir + "wineserver"

def path(self, dir: str) -> str: # noqa: D102
return self.base_dir + dir


def get_umu_proton(
env: dict[str, str], thread_pool: ThreadPoolExecutor
) -> dict[str, str]:
Expand Down
80 changes: 75 additions & 5 deletions umu/umu_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
)
from umu_log import CustomFormatter, console_handler, log
from umu_plugins import set_env_toml
from umu_proton import get_umu_proton
from umu_proton import Proton, get_umu_proton
from umu_runtime import setup_umu
from umu_util import get_libc
from umu_util import get_libc, is_installed_verb, is_winetricks_verb

THREAD_POOL: ThreadPoolExecutor = ThreadPoolExecutor()

Expand All @@ -46,11 +46,30 @@ def parse_args() -> Namespace | tuple[str, list[str]]: # noqa: D103
parser.add_argument(
"--config", help=("path to TOML file (requires Python 3.11+)")
)
parser.add_argument(
"winetricks",
help=("run winetricks (requires UMU-Proton or GE-Proton)"),
nargs="?",
default=None,
)

if not sys.argv[1:]:
parser.print_help(sys.stderr)
sys.exit(1)

# Winetricks
# Exit if no winetricks verbs were passed
if sys.argv[1].endswith("winetricks") and not sys.argv[2:]:
err: str = "No winetricks verb specified"
log.error(err)
sys.exit(1)

# Exit if argument is not a verb
if sys.argv[1].endswith("winetricks") and not is_winetricks_verb(
sys.argv[2:]
):
sys.exit(1)

if sys.argv[1:][0] in opt_args:
return parser.parse_args(sys.argv[1:])

Expand Down Expand Up @@ -80,8 +99,6 @@ def set_log() -> None:
log.addHandler(console_handler)
log.setLevel(level=DEBUG)

os.environ.pop("UMU_LOG")


def setup_pfx(path: str) -> None:
"""Create a symlink to the WINE prefix and tracked_files file."""
Expand Down Expand Up @@ -203,6 +220,21 @@ def set_env(
env["EXE"] = ""
env["STEAM_COMPAT_INSTALL_PATH"] = ""
env["PROTON_VERB"] = "waitforexitandrun"
elif isinstance(args, tuple) and args[0] == "winetricks":
# Make an absolute path to winetricks that is within GE-Proton or
# UMU-Proton, which includes the dependencies bundled within the
# protonfixes directory. Fixes exit 3 status codes after applying
# winetricks verbs
bin: str = (
Path(env["PROTONPATH"], "protonfixes", "winetricks")
.expanduser()
.resolve(strict=True)
.as_posix()
)
log.debug("EXE: %s -> %s", args[0], bin)
args: tuple[str, list[str]] = (bin, args[1])
env["EXE"] = bin
env["STEAM_COMPAT_INSTALL_PATH"] = Path(env["EXE"]).parent.as_posix()
elif isinstance(args, tuple):
try:
env["EXE"] = (
Expand Down Expand Up @@ -257,6 +289,24 @@ def set_env(
# Game drive
enable_steam_game_drive(env)

# Winetricks
if env.get("EXE").endswith("winetricks"):
proton: Proton = Proton(os.environ["PROTONPATH"])
env["WINE"] = proton.wine_bin
env["WINELOADER"] = proton.wine_bin
env["WINESERVER"] = proton.wineserver_bin
env["WINETRICKS_LATEST_VERSION_CHECK"] = "disabled"
env["LD_PRELOAD"] = ""
env["WINEDLLPATH"] = ":".join(
[
Path(proton.lib_dir, "wine").as_posix(),
Path(proton.lib64_dir, "wine").as_posix(),
]
)
env["WINETRICKS_SUPER_QUIET"] = (
"" if os.environ.get("UMU_LOG") == "debug" else "1"
)

return env


Expand Down Expand Up @@ -338,6 +388,12 @@ def build_command(
err: str = "The following file was not found in PROTONPATH: proton"
raise FileNotFoundError(err)

# Configure winetricks to not be prompted for any windows
if env.get("EXE").endswith("winetricks") and opts:
# The position of arguments matter for winetricks
# Usage: ./winetricks [options] [command|verb|path-to-verb] ...
opts = ["-q", *opts]

if opts:
command.extend(
[
Expand Down Expand Up @@ -378,6 +434,7 @@ def run_command(command: list[str]) -> int:
proc: Popen = None
ret: int = 0
libc: str = get_libc()
cwd: str = ""

if not command:
err: str = f"Command list is empty or None: {command}"
Expand All @@ -386,6 +443,12 @@ def run_command(command: list[str]) -> int:
if not libc:
log.warning("Will not set subprocess as subreaper")

# For winetricks, change directory to $PROTONPATH/protonfixes
if os.environ.get("EXE").endswith("winetricks"):
cwd = Path(os.environ.get("PROTONPATH"), "protonfixes").as_posix()
else:
cwd = Path.cwd().as_posix()

# Create a subprocess but do not set it as subreaper
# Unnecessary in a Flatpak and prctl() will fail if libc could not be found
if FLATPAK_PATH or not libc:
Expand All @@ -408,6 +471,7 @@ def run_command(command: list[str]) -> int:
command,
start_new_session=True,
preexec_fn=lambda: prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0, 0),
cwd=cwd,
)
ret = proc.wait()
log.debug("Child %s exited with wait status: %s", proc.pid, ret)
Expand Down Expand Up @@ -441,7 +505,7 @@ def main() -> int: # noqa: D103
"UMU_ZENITY": "",
}
command: list[str] = []
opts: list[str] = None
opts: list[str] = []
root: Path = Path(__file__).resolve(strict=True).parent
future: Future = None
args: Namespace | tuple[str, list[str]] = parse_args()
Expand Down Expand Up @@ -519,6 +583,12 @@ def main() -> int: # noqa: D103
future.result()
THREAD_POOL.shutdown()

# Exit if the winetricks verb is already installed to avoid reapplying it
if env.get("EXE").endswith("winetricks") and is_installed_verb(
opts, Path(env.get("WINEPREFIX"))
):
sys.exit(1)

# Run
build_command(env, UMU_LOCAL, command, opts)
log.debug("%s", command)
Expand Down
Loading

0 comments on commit f4fb158

Please sign in to comment.