feat(tray): add Wine tools submenu #281

Open
Boria138 wants to merge 1 commits from tray-wine into main
pull from: tray-wine
7 changed files with 394 additions and 57 deletions

No files matched your search

@@ -1572,6 +1572,7 @@ pw_kill_autostart () {
export -f pw_kill_autostart
stop_portproton () {
pw_remove_session_env
[[ "$int_xneur" == "1" ]] && xneur &
if [[ "$PW_USE_US_LAYOUT" == "1" ]] \
@@ -3845,6 +3846,7 @@ start_portproton () {
pw_mangohud_check
pw_vkbasalt_check
pw_lsfg_vk_check
pw_save_session_env || print_warning "Could not save Wine session environment"
}
pw_run () {
@@ -3996,6 +3998,80 @@ pw_run () {
}
export -f pw_run
pw_save_session_env () {
local session_dir="${PORT_DATA_PATH}/data/tmp/wine-session-$(id -u)"
local session_tmp
( umask 077; mkdir -p "$session_dir" ) || return 1
[[ ! -L "$session_dir" && -O "$session_dir" ]] || return 1
[[ "$(stat -c %a "$session_dir")" == 700 ]] || return 1
session_tmp="$(mktemp "$session_dir/env.XXXXXX")" || return 1
if ( export PW_TERM; export -p > "$session_tmp" ) \
&& mv -fT "$session_tmp" "$session_dir/session.env" ; then
return 0
fi
rm -f "$session_tmp"
return 1
}
pw_remove_session_env () {
local session_dir="${PORT_DATA_PATH}/data/tmp/wine-session-$(id -u)"
[[ ! -L "$session_dir" && -O "$session_dir" ]] || return 0
local session_env="$session_dir/session.env"
[[ ! -L "$session_env" && -O "$session_env" ]] || return 0
if grep -qsx "declare -x PW_START_PID=\"${PW_START_PID}\"" "$session_env" ; then
rm -f "$session_env"
fi
}
pw_session_tool () {
local tool="$1" session_pid session_env
local session_dir="${PORT_DATA_PATH}/data/tmp/wine-session-$(id -u)"
local session_wait_seconds="${2:-30}" wait_left
wait_left="$session_wait_seconds"
case "$tool" in
winefile|taskmgr|winecfg|regedit|cmd|uninstaller) ;;
*) print_error "Unsupported Wine tool: $tool" ; return 1 ;;
esac
session_env="$session_dir/session.env"
while (( wait_left > 0 )) ; do
if [[ ! -L "$session_dir" && -O "$session_dir" ]] \
&& [[ "$(stat -c %a "$session_dir")" == 700 ]] \
&& [[ ! -L "$session_env" && -O "$session_env" ]] \
&& [[ "$(stat -c %a "$session_env")" == 600 ]] \
&& session_pid="$(sed -n 's/^declare -x PW_START_PID="\([0-9]*\)"$/\1/p' "$session_env")" \
&& [[ -n "$session_pid" ]] \
&& kill -0 "$session_pid" 2>/dev/null ; then
break
fi
sleep 1
(( --wait_left ))
done
if (( wait_left == 0 )) ; then
print_error "Wine session unavailable after ${session_wait_seconds} seconds"
return 2
fi
source "$session_env" || return 1
PATH_TO_GAME="${WINEPREFIX}/drive_c"
[[ -d "$PATH_TO_GAME" ]] || return 1
unset LAUNCH_PARAMETERS PW_RUN_AFTER_EXE PW_RUN_GAMESCOPE PW_USE_TERMINAL
PW_TMPFS_PATH="$(mktemp -d "$session_dir/tool.XXXXXX")" || return 1
export PW_TMPFS_PATH
export PW_LOG_FILE="$PW_TMPFS_PATH/wine.log"
[[ "$tool" == cmd ]] && PW_USE_TERMINAL=1
[[ "$tool" == winecfg || "$tool" == regedit ]] && export GST_PLUGIN_SYSTEM_PATH_1_0=""
pw_run "$tool" || return 1
}
pw_launch_wrapper () {
if [[ -z "${PW_RUN_AFTER_EXE:-}" ]] ; then
"$@"
+31 -36
View File
@@ -67,6 +67,21 @@ export PW_LOG_FILE="${PORT_DATA_PATH}/PortProton.log"
# shellcheck source=/dev/null
source "$PORT_SCRIPTS_PATH/functions_helper"
if [[ "${1:-}" == "cli" && "${2:-}" == "--wine-session-tool" ]] ; then
session_unavailable_status=2
session_wait_seconds=30
[[ -n "${4:-}" && -n "${5:-}" ]] && session_wait_seconds=1
if pw_session_tool "${3:-}" "$session_wait_seconds" ; then
exit 0
else
session_tool_status=$?
fi
if [[ "$session_tool_status" != "$session_unavailable_status" || -z "${4:-}" || -z "${5:-}" ]] ; then
python_module dbus_tools notify -i error "Wine" "Failed to start Wine tool: ${3:-}" &>/dev/null
exit 1
fi
fi
export PORT_WINE_TMP_PATH="${PORT_DATA_PATH}/data/tmp"
create_new_dir "$PORT_WINE_TMP_PATH"
rm -f "$PORT_WINE_TMP_PATH"/*.{exe,msi,tar}*
@@ -280,11 +295,7 @@ case "$1" in
--show-ppdb Show the content of .ppdb file for specified .exe file
--backup-prefix Backup specified prefix to a file
--restore-prefix Restore prefix from backup file
--winefile Open wine file explorer, requires WINE version and prefix name
--winecfg Open wine configuration, requires WINE version and prefix name
--winecmd Open wine command prompt, requires WINE version and prefix name
--winereg Open wine registry editor, requires WINE version and prefix name
--wine_uninstaller Open wine uninstaller, requires WINE version and prefix name
--wine-session-tool Open a Wine tool in the current session, or use optional WINE and prefix
--winetricks-list-all List winetricks DLLs, fonts, and settings
--winetricks-install Install winetricks items, requires WINE version and prefix name
--clear_pfx Clear specified prefix, requires WINE version and prefix name
@@ -300,7 +311,7 @@ Usage examples:
portproton cli --del-user-conf PW_MANGOHUD
portproton cli --backup-prefix DEFAULT /path/to/backup/directory
portproton cli --restore-prefix /path/to/backup/file.ppack
portproton cli --winecfg WINE_LG DEFAULT
portproton cli --wine-session-tool winecfg WINE_LG DEFAULT
portproton cli --winetricks-list-all WINE_LG DEFAULT
portproton cli --winetricks-install WINE_LG DEFAULT --no-force corefonts
portproton cli --mangohud-preview \"fps,frametime,cpu_temp,gpu_temp\"
@@ -462,37 +473,21 @@ Usage examples:
pw_unpack_prefix "$2"
exit $?
;;
--winefile)
get_wine_and_pfx "$2" "$3"
--wine-session-tool)
tool="$2"
case "$tool" in
winefile|taskmgr|winecfg|regedit|cmd|uninstaller) ;;
*) print_error "Unsupported Wine tool: $tool" ; exit 1 ;;
esac
get_wine_and_pfx "$3" "$4"
start_portproton
pw_run winefile
stop_portproton
;;
--winecfg)
get_wine_and_pfx "$2" "$3"
start_portproton
export GST_PLUGIN_SYSTEM_PATH_1_0=""
pw_run winecfg
stop_portproton
;;
--winecmd)
get_wine_and_pfx "$2" "$3"
start_portproton
cd "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/drive_c" || fatal "Prefix drive_c directory not found"
PW_USE_TERMINAL=1 pw_run cmd
stop_portproton
;;
--winereg)
get_wine_and_pfx "$2" "$3"
start_portproton
export GST_PLUGIN_SYSTEM_PATH_1_0=""
pw_run regedit
stop_portproton
;;
--wine_uninstaller)
get_wine_and_pfx "$2" "$3"
start_portproton
pw_run uninstaller
[[ "$tool" == winecfg || "$tool" == regedit ]] && export GST_PLUGIN_SYSTEM_PATH_1_0=""
if [[ "$tool" == cmd ]] ; then
cd "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/drive_c" || fatal "Prefix drive_c directory not found"
PW_USE_TERMINAL=1 pw_run "$tool"
else
pw_run "$tool"
fi
stop_portproton
;;
--winetricks-list-all)
-1
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env bash
# Generate shell completion files for bash, zsh, and fish
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+8 -16
View File
@@ -160,11 +160,12 @@ class MainWindowWineTabMixin(_MainWindowTypingBase):
tools_grid.setSpacing(6)
tools = [
("--winecfg", _("Wine Configuration")),
("--winereg", _("Registry Editor")),
("--winefile", _("File Explorer")),
("--winecmd", _("Command Prompt")),
("--wine_uninstaller", _("Uninstaller")),
("winecfg", _("Wine Configuration")),
("regedit", _("Registry Editor")),
("winefile", _("File Explorer")),
("cmd", _("Command Prompt")),
("uninstaller", _("Uninstaller")),
("taskmgr", _("Task Manager")),
]
for i, (tool_cmd, tool_name) in enumerate(tools):
@@ -237,7 +238,7 @@ class MainWindowWineTabMixin(_MainWindowTypingBase):
env_vars = os.environ.copy()
self._check_missing_prefix_by_name_before_launch(prefix, env_vars)
start_sh = self.start_sh
cmd = start_sh + ["cli", cli_arg, wine, prefix]
cmd = start_sh + ["cli", "--wine-session-tool", cli_arg, wine, prefix]
# Show progress bar before launch
self.wine_progress_bar.setVisible(True)
@@ -269,16 +270,7 @@ class MainWindowWineTabMixin(_MainWindowTypingBase):
def _check_wine_process(self, cli_arg):
"""Check if target .exe process started."""
exe_map = {
"--winecfg": "winecfg.exe",
"--winereg": "regedit.exe",
"--winefile": "winefile.exe",
"--winecmd": "cmd.exe",
"--wine_uninstaller": "uninstaller.exe",
}
target_exe = exe_map.get(cli_arg, "")
if not target_exe:
return
target_exe = f"{cli_arg}.exe"
# Check processes via psutil
for proc in psutil.process_iter(attrs=["name"]):
+40
View File
@@ -3,6 +3,7 @@ import subprocess
import signal
import psutil
import os
from PySide6.QtCore import QProcess
from PySide6.QtWidgets import QSystemTrayIcon, QMenu, QApplication, QMessageBox
from PySide6.QtGui import QIcon, QAction
from portprotonqt.logger import get_logger
@@ -45,6 +46,39 @@ def _restart_args(args: list[str]) -> list[str]:
return restart_args
def create_wine_tools_menu(parent: QMenu, start_sh: list[str]) -> QMenu:
"""Create tools that join the running game's Wine session."""
menu = QMenu("Wine", parent)
tools = (
("winefile", _("File Explorer")),
("taskmgr", _("Task Manager")),
("winecfg", _("Wine Configuration")),
("regedit", _("Registry Editor")),
("cmd", _("Command Prompt")),
("uninstaller", _("Uninstaller")),
)
for tool, title in tools:
action = menu.addAction(title)
action.triggered.connect(
lambda checked=False, name=tool: launch_session_wine_tool(start_sh, name)
)
return menu
def launch_session_wine_tool(start_sh: list[str], tool: str) -> None:
"""Start a Wine tool without running the game's startup or cleanup."""
if not start_sh:
logger.error("Cannot launch Wine tool: PortProton command is missing")
QMessageBox.warning(None, _("Error"), _("Failed to start process."))
return
started, _pid = QProcess.startDetached(
start_sh[0], start_sh[1:] + ["cli", "--wine-session-tool", tool]
)
if not started:
logger.error("Failed to start Wine tool %s", tool)
QMessageBox.warning(None, _("Error"), _("Failed to start process."))
class TrayManager:
"""Tray management module for PortProtonQt.
@@ -83,8 +117,12 @@ class TrayManager:
self.pause_game_action.setEnabled(False)
self.pause_game_action.triggered.connect(self.toggle_game_pause)
self.wine_tools_menu = create_wine_tools_menu(self.tray_menu, self.main_window.start_sh)
self.wine_tools_menu.setEnabled(False)
self.tray_menu.addAction(self.pause_game_action)
self.tray_menu.addAction(self.stop_game_action)
self.tray_menu.addMenu(self.wine_tools_menu)
self.tray_menu.addSeparator()
self.tray_menu.addSeparator()
@@ -103,6 +141,7 @@ class TrayManager:
self.tray_menu.addAction(self.pause_game_action)
self.tray_menu.addAction(self.stop_game_action)
self.tray_menu.addMenu(self.wine_tools_menu)
self.update_game_actions()
if self.minimal_mode:
return
@@ -151,6 +190,7 @@ class TrayManager:
target_exe = getattr(self.main_window, "target_exe", None)
has_running_game = bool(game_processes or target_exe)
self.stop_game_action.setEnabled(has_running_game)
self.wine_tools_menu.setEnabled(has_running_game)
self.pause_game_action.setEnabled(bool(processes))
paused = self._are_processes_paused(processes)
self.pause_game_action.setText(_("Resume Game") if paused else _("Pause Game"))
+101 -4
View File
@@ -42,7 +42,7 @@ from portprotonqt.game_library_manager import FullLibraryTile, GameLibraryManage
from portprotonqt.input_manager import InputManager
from portprotonqt.main_window import MainWindow
from portprotonqt.search_utils import SearchOptimizer, search_index
from portprotonqt.tray_manager import TrayManager
from portprotonqt.tray_manager import TrayManager, create_wine_tools_menu, launch_session_wine_tool
from portprotonqt.themes.standart.styles.constants import GAME_CARD_ANIMATION
from portprotonqt.portproton_api import remove_empty_custom_data_dirs
import portprotonqt.tabs.autoinstall_tab as autoinstall_tab_module
@@ -374,18 +374,71 @@ def test_minimal_tray_contains_game_actions() -> None:
_application = QApplication.instance() or QApplication([])
manager = TrayManager.__new__(TrayManager)
manager.tray_menu = QMenu()
manager.pause_game_action = QAction("Pause Game", manager.tray_menu)
manager.stop_game_action = QAction("Stop Game", manager.tray_menu)
owner = QWidget()
manager.pause_game_action = QAction("Pause Game", owner)
manager.stop_game_action = QAction("Stop Game", owner)
manager.wine_tools_menu = create_wine_tools_menu(manager.tray_menu, ["portproton"])
manager.minimal_mode = True
manager.update_game_actions = MagicMock()
manager.refresh_tray_menu()
manager.refresh_tray_menu()
assert manager.tray_menu.actions() == [
manager.pause_game_action,
manager.stop_game_action,
manager.wine_tools_menu.menuAction(),
]
manager.update_game_actions.assert_called_once_with()
assert manager.update_game_actions.call_count == 2
@mark.parametrize("tool_index,tool", list(enumerate(
("winefile", "taskmgr", "winecfg", "regedit", "cmd", "uninstaller")
)))
def test_wine_tools_menu_launches_session_tool(
monkeypatch: MonkeyPatch, tool_index: int, tool: str,
) -> None:
_application = QApplication.instance() or QApplication([])
parent = QMenu()
start = MagicMock(return_value=(True, 123))
monkeypatch.setattr("portprotonqt.tray_manager.QProcess.startDetached", start)
menu = create_wine_tools_menu(parent, ["bash", "/path with spaces/start.sh"])
assert len(menu.actions()) == 6
menu.actions()[tool_index].trigger()
start.assert_called_once_with(
"bash", ["/path with spaces/start.sh", "cli", "--wine-session-tool", tool]
)
def test_wine_tool_start_failure_is_reported(monkeypatch: MonkeyPatch) -> None:
_application = QApplication.instance() or QApplication([])
warning = MagicMock()
monkeypatch.setattr("portprotonqt.tray_manager.QMessageBox.warning", warning)
monkeypatch.setattr(
"portprotonqt.tray_manager.QProcess.startDetached", lambda *args: (False, 0)
)
launch_session_wine_tool(["portproton"], "winefile")
warning.assert_called_once()
@mark.parametrize("running", [False, True])
def test_wine_tools_menu_requires_running_game(running: bool) -> None:
_application = QApplication.instance() or QApplication([])
manager = TrayManager.__new__(TrayManager)
manager.main_window = SimpleNamespace(game_processes=[], target_exe="game.exe" if running else None)
manager.stop_game_action = QAction()
manager.pause_game_action = QAction()
manager.wine_tools_menu = QMenu()
manager._game_process_tree = MagicMock(return_value=[])
manager.update_game_actions()
assert manager.wine_tools_menu.isEnabled() is running
def test_tray_pauses_game_process_tree(monkeypatch: MonkeyPatch) -> None:
child = MagicMock(pid=12)
@@ -3378,3 +3431,47 @@ def test_custom_combo_opens_readonly_field_on_mouse_release(readonly: bool) -> N
assert combo.view().isVisible() is readonly
combo.hidePopup()
combo.close()
@mark.parametrize("tool", ["winecfg", "regedit", "winefile", "cmd", "uninstaller", "taskmgr"])
@mark.parametrize("running", [False, True])
def test_wine_settings_tools_use_unified_command(
monkeypatch: MonkeyPatch, tool: str, running: bool,
) -> None:
process = MagicMock()
monkeypatch.setattr("portprotonqt.tabs.wine_tab.QProcess", MagicMock(return_value=process))
window = MagicMock()
window.game_processes = [MagicMock()] if running else []
window.target_exe = "game.exe" if running else None
window.start_sh = ["bash", "/path with spaces/start.sh"]
window.wineCombo.currentText.return_value = "WINE_LG"
window.prefixCombo.currentText.return_value = "my prefix"
WineMixin.launch_generic_tool(window, tool)
process.start.assert_called_once_with(
"bash", ["/path with spaces/start.sh", "cli", "--wine-session-tool", tool, "WINE_LG", "MY_PREFIX"],
)
window._start_wine_process_monitor.assert_called_once_with(tool)
def test_silent_tray_includes_wine_tools_menu(monkeypatch: MonkeyPatch) -> None:
_application = QApplication.instance() or QApplication([])
manager = TrayManager.__new__(TrayManager)
manager.tray_menu = QMenu()
owner = QWidget()
manager.pause_game_action = QAction("Pause Game", owner)
manager.stop_game_action = QAction("Stop Game", owner)
manager.wine_tools_menu = create_wine_tools_menu(manager.tray_menu, ["portproton"])
manager.update_game_actions = MagicMock()
start = MagicMock(return_value=(True, 123))
monkeypatch.setattr("portprotonqt.tray_manager.QProcess.startDetached", start)
manager.set_minimal_mode()
tools = manager.tray_menu.actions()[2].menu()
assert isinstance(tools, QMenu)
assert len(tools.actions()) == 6
tools.actions()[1].trigger()
start.assert_called_once_with("portproton", ["cli", "--wine-session-tool", "taskmgr"])
+138
View File
@@ -232,3 +232,141 @@ def test_executable_metadata_uses_pefile_and_preserves_lnk_fallback(
if expected_calls == ["pe"]:
assert "File Description: Description" in metadata
assert "Company Name: Sony" in metadata
def test_wine_session_missing_returns_distinct_status(tmp_path: Path) -> None:
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text()
definition = helper.split("pw_session_tool () {", 1)[1].split("pw_launch_wrapper () {", 1)[0]
script = "pw_session_tool () {" + definition + '''
print_error () { echo "$@" >&2; }
sleep () { return 0; }
pw_session_tool taskmgr 1
'''
result = subprocess.run(
["bash", "-c", script], cwd=tmp_path, capture_output=True, text=True, check=False,
env={"PATH": os.defpath, "PORT_DATA_PATH": str(tmp_path)},
)
assert result.returncode == 2
@mark.parametrize("tool", ["winecfg", "regedit", "winefile", "cmd", "uninstaller", "taskmgr"])
def test_wine_session_tool_standalone_uses_selected_wine_and_prefix(tmp_path: Path, tool: str) -> None:
start = Path("build-aux/share/portproton/scripts/start.sh").read_text()
block = start.split(" --wine-session-tool)\n", 1)[1].split(" --winetricks-list-all)", 1)[0]
script = '''
get_wine_and_pfx () { printf '%s\\n' "$@"; }
start_portproton () { mkdir -p "$PORT_DATA_PATH/data/prefixes/$PW_PREFIX_NAME/drive_c"; echo start; }
pw_run () { printf '%s\\n' "$@"; }
stop_portproton () { echo stop; }
''' + 'case "$1" in --wine-session-tool)\n' + block + '\nesac\n'
result = subprocess.run(
["bash", "-c", script, "bash", "--wine-session-tool", tool, "WINE_LG", "MY_PREFIX"],
cwd=tmp_path, capture_output=True, text=True, check=False,
env={"PATH": os.defpath, "PORT_DATA_PATH": str(tmp_path), "PW_PREFIX_NAME": "MY_PREFIX"},
)
assert result.returncode == 0, result.stderr
assert result.stdout.splitlines() == ["WINE_LG", "MY_PREFIX", "start", tool, "stop"]
@mark.parametrize("tool", ["winefile", "taskmgr", "winecfg", "regedit", "cmd", "uninstaller"])
@mark.parametrize("tool_exit_code", [0, 2])
def test_wine_session_tools_reuse_prefix_without_game_options(
tmp_path: Path, tool: str, tool_exit_code: int,
) -> None:
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text()
definitions = helper.split("pw_save_session_env () {", 1)[1].split("pw_launch_wrapper () {", 1)[0]
script = "pw_save_session_env () {" + definitions + '''
print_error () { echo "$@" >&2; }
pw_run () {
printf '%s\\n' "$1" "$WINEPREFIX" "${PW_USE_TERMINAL:-0}" "${LAUNCH_PARAMETERS:-}" \\
"${PW_RUN_AFTER_EXE:-}" "${PW_RUN_GAMESCOPE:-}" "$PW_TMPFS_PATH"
return "$TOOL_EXIT_CODE"
}
PW_TERM="test-terminal"
export PW_START_PID="$$" WINEPREFIX="$PORT_DATA_PATH/prefix with spaces"
export LAUNCH_PARAMETERS="--game-only" PW_RUN_AFTER_EXE=trainer.exe PW_RUN_GAMESCOPE=gamescope
pw_save_session_env || exit 2
pw_session_tool "$1"
'''
(tmp_path / "prefix with spaces/drive_c").mkdir(parents=True)
result = subprocess.run(
["bash", "-c", script, "bash", tool], capture_output=True, text=True,
env={"PATH": os.defpath, "PORT_DATA_PATH": str(tmp_path),
"TOOL_EXIT_CODE": str(tool_exit_code)}, check=False,
)
assert result.returncode == int(tool_exit_code != 0), result.stderr
lines = result.stdout.splitlines()
assert lines[:6] == [tool, str(tmp_path / "prefix with spaces"), "1" if tool == "cmd" else "0", "", "", ""]
assert Path(lines[6]).is_dir()
assert Path(lines[6]).stat().st_mode & 0o777 == 0o700
session = next((tmp_path / "data/tmp").glob("wine-session-*/session.env"))
assert session.stat().st_mode & 0o777 == 0o600
assert 'declare -x PW_TERM="test-terminal"' in session.read_text()
@mark.parametrize("unsafe_directory", [False, True])
def test_wine_session_save_does_not_follow_symlinks(tmp_path: Path, unsafe_directory: bool) -> None:
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text()
definition = helper.split("pw_save_session_env () {", 1)[1].split("pw_remove_session_env () {", 1)[0]
session_dir = tmp_path / "data/tmp" / f"wine-session-{os.getuid()}"
session_dir.parent.mkdir(parents=True)
victim = tmp_path / "victim"
victim.write_text("keep this")
if unsafe_directory:
target = tmp_path / "other-directory"
target.mkdir(mode=0o700)
session_dir.symlink_to(target, target_is_directory=True)
else:
session_dir.mkdir(mode=0o700)
(session_dir / "session.env").symlink_to(victim)
result = subprocess.run(
["bash", "-c", "pw_save_session_env () {" + definition + "\npw_save_session_env"],
capture_output=True, text=True,
env={"PATH": os.defpath, "PORT_DATA_PATH": str(tmp_path)}, check=False,
)
assert victim.read_text() == "keep this"
assert result.returncode == (1 if unsafe_directory else 0), result.stderr
if not unsafe_directory:
assert not (session_dir / "session.env").is_symlink()
def test_wine_session_tool_rejects_unknown_tool_before_reading_environment(tmp_path: Path) -> None:
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text()
definition = helper.split("pw_session_tool () {", 1)[1].split("pw_launch_wrapper () {", 1)[0]
script = "pw_session_tool () {" + definition + '''
print_error () { echo "$@" >&2; }
sleep () { exit 99; }
pw_session_tool '../../other-command'
'''
result = subprocess.run(
["bash", "-c", script], capture_output=True, text=True,
env={"PATH": os.defpath, "PORT_DATA_PATH": str(tmp_path)}, check=False,
)
assert result.returncode == 1
assert "Unsupported Wine tool" in result.stderr
def test_wine_session_cleanup_preserves_newer_session(tmp_path: Path) -> None:
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text()
definitions = helper.split("pw_save_session_env () {", 1)[1].split("pw_session_tool () {", 1)[0]
script = "pw_save_session_env () {" + definitions + '''
export PW_START_PID="$$"
pw_save_session_env || exit 2
PW_START_PID=99999999
pw_remove_session_env
[[ -f "$PORT_DATA_PATH/data/tmp/wine-session-$(id -u)/session.env" ]] || exit 3
PW_START_PID="$$"
pw_remove_session_env
[[ ! -e "$PORT_DATA_PATH/data/tmp/wine-session-$(id -u)/session.env" ]]
'''
result = subprocess.run(
["bash", "-c", script], capture_output=True, text=True,
env={"PATH": os.defpath, "PORT_DATA_PATH": str(tmp_path)}, check=False,
)
assert result.returncode == 0, result.stderr