diff --git a/build-aux/share/portproton/scripts/functions_helper b/build-aux/share/portproton/scripts/functions_helper index f0eb6d02..10818f9e 100755 --- a/build-aux/share/portproton/scripts/functions_helper +++ b/build-aux/share/portproton/scripts/functions_helper @@ -3770,10 +3770,12 @@ pw_run () { echo "Log WINE:" >> "${PW_LOG_FILE}" echo "" print_info "Log from RUNTIME and WINE:" + pw_launch_wrapper \ ${PW_RUN_GAMESCOPE} \ ${PW_INHIBIT_SLR} \ ${PW_TASKSET_SLR} \ ${pw_runtime} \ + env \ LD_LIBRARY_PATH="${PW_LD_LIBRARY_PATH}" \ LD_PRELOAD="${PW_LD_PRELOAD}" \ VK_ADD_IMPLICIT_LAYER_PATH="${PW_VK_LAYER_PATH}" \ @@ -3788,10 +3790,12 @@ pw_run () { fi echo "" echo "Log WINE:" > "${PW_LOG_FILE}" + pw_launch_wrapper \ ${PW_RUN_GAMESCOPE} \ ${PW_INHIBIT_SLR} \ ${PW_TASKSET_SLR} \ ${pw_runtime} \ + env \ LD_LIBRARY_PATH="${PW_LD_LIBRARY_PATH}" \ LD_PRELOAD="${PW_LD_PRELOAD}" \ VK_ADD_IMPLICIT_LAYER_PATH="${PW_VK_LAYER_PATH}" \ @@ -3816,6 +3820,7 @@ pw_run () { echo "Log WINE:" >> "${PW_LOG_FILE}" echo "" print_info "Log WINE:" + pw_launch_wrapper \ env FAKE_VAR= \ LD_LIBRARY_PATH="${PW_LD_LIBRARY_PATH}" \ LD_PRELOAD="${PW_LD_PRELOAD}" \ @@ -3835,6 +3840,7 @@ pw_run () { echo "" echo "Log WINE:" > "${PW_LOG_FILE}" print_info "Log WINE:" + pw_launch_wrapper \ env FAKE_VAR= \ LD_LIBRARY_PATH="${PW_LD_LIBRARY_PATH}" \ LD_PRELOAD="${PW_LD_PRELOAD}" \ @@ -3853,6 +3859,39 @@ pw_run () { } export -f pw_run +pw_launch_wrapper () { + if [[ -z "${PW_RUN_AFTER_EXE:-}" ]] ; then + "$@" + return + fi + + if [[ -f "${PW_RUN_AFTER_EXE}" ]] ; then + if [[ "${PW_RUN_AFTER_EXE,,}" =~ \.exe$ ]] ; then + "$@" + return + fi + "${PW_RUN_AFTER_EXE}" + "$@" + return + fi + + local -a wrapper_cmd + eval "wrapper_cmd=(${PW_RUN_AFTER_EXE})" + if [[ ${#wrapper_cmd[@]} -eq 0 ]] ; then + "$@" + return + fi + + if [[ "${wrapper_cmd[0]##*/}" == "systemd-run" ]] ; then + "${wrapper_cmd[@]}" -- "$@" + return + fi + + "${wrapper_cmd[@]}" + "$@" +} +export -f pw_launch_wrapper + steamplay_launch () { pw_check_and_download_plugins @@ -3969,6 +4008,7 @@ steamplay_launch () { fi print_info "$proton_runner in use." + pw_launch_wrapper \ ${PW_RUN_GAMESCOPE} \ env \ LD_LIBRARY_PATH="${steamplay_ld_library_path}" \ @@ -4012,6 +4052,7 @@ portwine_launch () { else if [[ -n "${PW_RUN_AFTER_EXE:-}" ]] \ && [[ -f "${PW_RUN_AFTER_EXE}" ]] \ + && [[ "${PW_RUN_AFTER_EXE,,}" =~ \.exe$ ]] \ && [[ "${PW_RUN_AFTER_EXE}" != "${PW_EXE_FILE}" ]] \ && [[ "${PW_EXE_FILE,,}" =~ \.exe$ ]] then diff --git a/portprotonqt/dialogs/settings_dialog.py b/portprotonqt/dialogs/settings_dialog.py index e8abe5e6..ecfb09b7 100644 --- a/portprotonqt/dialogs/settings_dialog.py +++ b/portprotonqt/dialogs/settings_dialog.py @@ -91,6 +91,11 @@ def _normalize_prefix_directories(prefixes_dir): logger.warning("Failed to rename prefix %s: %s", prefix_name, exc) +def _format_setting_value_for_display(value: str) -> str: + """Hide shell-escaped quotes in GUI fields.""" + return value.replace('\\"', '"') + + def _get_numa_nodes() -> dict[str, str]: """Read NUMA nodes from lscpu output.""" try: @@ -247,11 +252,25 @@ class ExeSettingsDialog(DraggableDialog, MangoHudSettingsMixin, GamescopeSetting normalized = os.path.normpath(os.path.expanduser(exe_path)) if os.path.isabs(normalized): return normalized + if " " in exe_path: + return exe_path game_dir = os.path.dirname(self.exe_path or "") if not game_dir: return normalized return os.path.normpath(os.path.join(game_dir, normalized)) + def _get_setting_file_selector_path(self, current_path: str) -> str: + """Get initial path for setting file selectors.""" + initial_path = os.path.expanduser("~") + if not current_path: + return initial_path + normalized = self._resolve_run_after_exe_path(current_path) + if os.path.isfile(normalized): + return os.path.dirname(normalized) + if os.path.isdir(normalized): + return normalized + return initial_path + def setup_ui(self): """Set up the user interface.""" self.main_layout = QVBoxLayout(self) @@ -451,6 +470,7 @@ class ExeSettingsDialog(DraggableDialog, MangoHudSettingsMixin, GamescopeSetting ): if val.startswith('"') and val.endswith('"') and len(val) >= 2: val = val[1:-1] + val = _format_setting_value_for_display(val) self.current_settings[key] = val except ValueError: continue @@ -736,23 +756,20 @@ class ExeSettingsDialog(DraggableDialog, MangoHudSettingsMixin, GamescopeSetting browse_button.setEnabled(False) def open_run_after_exe_selector( - _checked: bool = False, target_line_edit=line_edit + _checked: bool = False, + target_line_edit=line_edit, + file_filter="", ): from portprotonqt.dialogs.file_explorer import FileExplorer - initial_path = os.path.expanduser("~") - current_path = target_line_edit.text().strip() - if current_path: - normalized = self._resolve_run_after_exe_path(current_path) - if os.path.isfile(normalized): - initial_path = os.path.dirname(normalized) - elif os.path.isdir(normalized): - initial_path = normalized + initial_path = self._get_setting_file_selector_path( + target_line_edit.text().strip() + ) file_explorer = FileExplorer( self, theme=self.theme, - file_filter=".exe", + file_filter=file_filter, initial_path=initial_path, ) file_explorer.file_signal.file_selected.connect( diff --git a/portprotonqt/settings_manager.py b/portprotonqt/settings_manager.py index 839e5d2a..0fd8e2a5 100644 --- a/portprotonqt/settings_manager.py +++ b/portprotonqt/settings_manager.py @@ -253,11 +253,11 @@ def get_advanced_settings(disabled_text, logical_core_options=None, locale_optio 'default': '' }) - # 7. Run second executable after launch + # 7. Run second executable or wrapper command advanced_settings.append({ 'key': 'PW_RUN_AFTER_EXE', - 'name': _("Run After Exe"), - 'description': _("Path to a second .exe that should be started after the main executable."), + 'name': _("Run After / Wrapper"), + 'description': _("Path to a second .exe, script, or wrapper command. Example: systemd-run --user --scope --slice=app.slice"), 'type': 'text', 'default': '' })