feat(scripts): added cli to winetricks #188

Manually merged
CastroFidel merged 1 commits from scripts-winetrics into main 2026-06-03 17:07:56 +00:00
3 changed files with 76 additions and 97 deletions

No files matched your search

+24 -1
View File
@@ -38,7 +38,7 @@ if [[ -z "$PORT_DATA_PATH" ]] ; then
fi
if [[ -f "$HOME/.config/PortProtonQt.conf" ]] \
&& grep "disable_runtime_download = True" "$HOME/.config/PortProtonQt.conf" ; then
&& grep -q "disable_runtime_download = True" "$HOME/.config/PortProtonQt.conf" ; then
export PW_DISABLE_RUNTIME_DOWNLOAD=1
fi
@@ -459,6 +459,29 @@ Usage examples:
pw_run uninstaller
stop_portproton
;;
--winetricks-list)
get_wine_and_pfx "$2" "$3"
start_portproton >&2
update_winetricks >&2
"${PORT_WINE_TMP_PATH}/winetricks" "$4" list
winetricks_status=$?
stop_portproton >&2
exit "$winetricks_status"
;;
--winetricks-install)
get_wine_and_pfx "$2" "$3"
winetricks_force="$4"
shift 4
start_portproton
update_winetricks
winetricks_args=("--unattended")
[[ "$winetricks_force" == "--force" ]] && winetricks_args+=("--force")
"${PORT_WINE_TMP_PATH}/winetricks" "${winetricks_args[@]}" "$@"
winetricks_status=$?
wait_wineserver
stop_portproton
exit "$winetricks_status"
;;
--clear_pfx)
get_wine_and_pfx "$2" "$3"
pw_clear_pfx
+51 -94
View File
@@ -20,7 +20,6 @@ from portprotonqt.logger import get_logger
from portprotonqt.theme_manager import ThemeManager
from portprotonqt.custom_widgets import AutoSizeButton
from portprotonqt.dialogs.base import DraggableDialog
from portprotonqt.downloader import Downloader
from portprotonqt.preloader import Preloader
from portprotonqt.localization import _
from portprotonqt.dialogs.dialog_utils import create_dialog_hints_widget, update_dialog_hints
@@ -37,13 +36,11 @@ class WinetricksDialog(DraggableDialog):
self.theme = theme if theme else theme_manager.apply_theme(ui_config.get_theme())
self.prefix_path: str | None = prefix_path
self.wine_use: str | None = wine_use
self.start_sh: list[str] | None = None
self.portproton_path = get_portproton_location()
if self.portproton_path is None:
logger.error("PortProton location not found")
return
self.tmp_path = os.path.join(self.portproton_path, "data", "tmp")
os.makedirs(self.tmp_path, exist_ok=True)
self.winetricks_path = os.path.join(self.tmp_path, "winetricks")
if self.prefix_path is None:
logger.error("Prefix path not provided")
return
@@ -52,15 +49,20 @@ class WinetricksDialog(DraggableDialog):
if not os.path.exists(self.log_path):
open(self.log_path, 'a').close()
self.downloader = Downloader(max_workers=4)
self.apply_process: QProcess | None = None
self.list_processes: dict[str, QProcess] = {}
parent_obj = self.parent()
while parent_obj:
if hasattr(parent_obj, 'start_sh'):
self.start_sh = cast("MainWindow", parent_obj).start_sh
break
parent_obj = parent_obj.parent()
self.setWindowTitle(_("Prefix Manager"))
self.setModal(True)
self.resize(700, 700)
self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE + self.theme.MESSAGE_BOX_STYLE)
self.update_winetricks()
self.setup_ui()
self.load_lists()
@@ -92,75 +94,6 @@ class WinetricksDialog(DraggableDialog):
)
update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name)
def update_winetricks(self):
"""Update the winetricks script."""
url = "https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks"
temp_path = os.path.join(self.tmp_path, "winetricks_temp")
try:
if not self.downloader.download(url, temp_path):
logger.warning("Failed to download external winetricks version")
if os.path.exists(temp_path):
os.remove(temp_path)
return
with open(temp_path) as f:
ext_content = f.read()
ext_ver_match = re.search(r'WINETRICKS_VERSION\s*=\s*[\'"]?([^\'"\s]+)', ext_content)
ext_ver = ext_ver_match.group(1) if ext_ver_match else None
logger.info(f"External winetricks version: {ext_ver}")
except Exception as e:
logger.error(f"Failed to get external version: {e}")
ext_ver = None
if os.path.exists(temp_path):
os.remove(temp_path)
return
int_ver = None
if os.path.exists(self.winetricks_path):
try:
with open(self.winetricks_path) as f:
int_content = f.read()
int_ver_match = re.search(r'WINETRICKS_VERSION\s*=\s*[\'"]?([^\'"\s]+)', int_content)
int_ver = int_ver_match.group(1) if int_ver_match else None
logger.info(f"Internal winetricks version: {int_ver}")
except Exception as e:
logger.error(f"Failed to read internal winetricks version: {e}")
update_needed = not os.path.exists(self.winetricks_path) or (int_ver != ext_ver and ext_ver)
if update_needed:
try:
self.downloader.download(url, self.winetricks_path)
os.chmod(self.winetricks_path, 0o755)
logger.info(f"Winetricks updated to version {ext_ver}")
self.apply_modifications(self.winetricks_path)
except Exception as e:
logger.error(f"Failed to update winetricks: {e}")
elif os.path.exists(self.winetricks_path):
self.apply_modifications(self.winetricks_path)
if os.path.exists(temp_path):
os.remove(temp_path)
def apply_modifications(self, file_path):
"""Apply custom modifications to the winetricks script."""
if not os.path.exists(file_path):
return
try:
with open(file_path) as f:
content = f.read()
content = re.sub(r'w_metadata vcrun2015 dlls \\', r'w_metadata !dont_use_2015! dlls \\', content)
content = re.sub(r'w_metadata vcrun2017 dlls \\', r'w_metadata !dont_use_2017! dlls \\', content)
content = re.sub(r'w_metadata vcrun2019 dlls \\', r'w_metadata !dont_use_2019! dlls \\', content)
content = re.sub(r'w_set_winver win2k3', r'w_set_winver win7', content)
with open(file_path, 'w') as f:
f.write(content)
logger.info("Winetricks modifications applied")
except Exception as e:
logger.error(f"Error applying modifications to winetricks: {e}")
def setup_ui(self):
"""Set up the user interface with tabs and tables."""
self.main_layout = QVBoxLayout(self)
@@ -301,38 +234,36 @@ class WinetricksDialog(DraggableDialog):
def load_lists(self):
"""Load and populate the lists for DLLs, Fonts, and Settings."""
if not os.path.exists(self.winetricks_path):
if self.start_sh is None or self.wine_use is None:
QMessageBox.warning(self, _("Error"), _("Winetricks not found. Please try again."))
self.reject()
return
assert self.prefix_path is not None
env = QProcessEnvironment.systemEnvironment()
env.insert("WINEPREFIX", self.prefix_path)
env.insert("WINETRICKS_DOWNLOADER", "curl")
if self.wine_use is not None:
env.insert("WINE", self.wine_use)
cwd = os.path.dirname(self.winetricks_path)
self.containers["dlls"].setCurrentIndex(0)
self._start_list_process("dlls", self.dll_table, self.get_dll_exclusions(), env, cwd)
self._start_list_process("dlls", self.dll_table, self.get_dll_exclusions(), env)
self.containers["fonts"].setCurrentIndex(0)
self._start_list_process("fonts", self.fonts_table, self.get_fonts_exclusions(), env, cwd)
self._start_list_process("fonts", self.fonts_table, self.get_fonts_exclusions(), env)
self.containers["settings"].setCurrentIndex(0)
self._start_list_process("settings", self.settings_table, self.get_settings_exclusions(), env, cwd)
self._start_list_process("settings", self.settings_table, self.get_settings_exclusions(), env)
def _start_list_process(self, category, table, exclusion_pattern, env, cwd):
def _start_list_process(self, category, table, exclusion_pattern, env):
"""Start QProcess for list."""
assert self.prefix_path is not None
assert self.start_sh is not None
assert self.wine_use is not None
process = QProcess(self)
process.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels)
process.setProcessEnvironment(env)
process.finished.connect(
lambda exit_code, exit_status: self._on_list_finished(category, table, exclusion_pattern, process, exit_code, exit_status)
)
process.start(self.winetricks_path, [category, "list"])
prefix_name = os.path.basename(self.prefix_path.rstrip(os.sep))
args = self.start_sh[1:] + ["cli", "--winetricks-list", self.wine_use, prefix_name, category]
self.list_processes[category] = process
process.start(self.start_sh[0], args)
def _on_list_finished(self, category, table, exclusion_pattern, process: QProcess | None, exit_code, exit_status):
"""Handle list completion."""
@@ -350,6 +281,8 @@ class WinetricksDialog(DraggableDialog):
error_output = bytes(process.readAllStandardError().data()).decode('utf-8', 'ignore')
logger.error(f"Failed to list {category}: {error_output}")
self.list_processes.pop(category, None)
process.deleteLater()
self.containers[category].setCurrentIndex(1)
def get_dll_exclusions(self):
@@ -379,6 +312,7 @@ class WinetricksDialog(DraggableDialog):
for line in lines:
line = line.strip()
line = re.sub(r'(?:\x1b|\033)?\[[0-9;]*m', '', line).strip()
if not line or re.search(exclusion_pattern, line, re.I):
continue
@@ -446,10 +380,9 @@ class WinetricksDialog(DraggableDialog):
def _start_install_process(self, selected, force):
"""Start QProcess for install."""
assert self.prefix_path is not None
if self.start_sh is None or self.wine_use is None:
return
env = QProcessEnvironment.systemEnvironment()
env.insert("WINEPREFIX", self.prefix_path)
if self.wine_use is not None:
env.insert("WINE", self.wine_use)
self.apply_process = QProcess(self)
self.apply_process.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels)
@@ -458,8 +391,16 @@ class WinetricksDialog(DraggableDialog):
self.apply_process.finished.connect(
lambda exit_code, exit_status: self._on_install_finished(exit_code, exit_status, selected)
)
args = ["--unattended"] + (["--force"] if force else []) + selected
self.apply_process.start(self.winetricks_path, args)
prefix_name = os.path.basename(self.prefix_path.rstrip(os.sep))
force_arg = "--force" if force else "--no-force"
args = self.start_sh[1:] + [
"cli",
"--winetricks-install",
self.wine_use,
prefix_name,
force_arg,
] + selected
self.apply_process.start(self.start_sh[0], args)
def _on_ready_read(self):
"""Handle ready read for install process."""
@@ -506,14 +447,30 @@ class WinetricksDialog(DraggableDialog):
self.log_output.append(message)
self.log_output.moveCursor(QTextCursor.MoveOperation.End)
def _stop_processes(self):
"""Stop running child processes."""
processes = list(self.list_processes.values())
if self.apply_process is not None:
processes.append(self.apply_process)
for process in processes:
if process.state() == QProcess.ProcessState.NotRunning:
continue
process.terminate()
if not process.waitForFinished(1000):
process.kill()
self.list_processes.clear()
def closeEvent(self, event):
"""Disable mode on close."""
self._stop_processes()
if self.input_manager:
self.input_manager.disable_winetricks_mode()
super().closeEvent(event)
def reject(self):
"""Disable mode on reject."""
self._stop_processes()
if self.input_manager:
self.input_manager.disable_winetricks_mode()
super().reject()
+1 -2
View File
@@ -2246,10 +2246,9 @@ class MainWindow(MainWindowControlHintsMixin, MainWindowSystemTabMixin, MainWind
assert self.portproton_location is not None
prefix_path = os.path.join(self.portproton_location, "data", "prefixes", selected_prefix)
wine_path = os.path.join(self.portproton_location, "data", "dist", selected_wine, "bin", "wine")
# Open Winetricks dialog
dialog = WinetricksDialog(self, self.theme, prefix_path, wine_path)
dialog = WinetricksDialog(self, self.theme, prefix_path, selected_wine)
dialog.exec()
def createPortProtonTab(self):