diff --git a/bottles/backend/managers/manager.py b/bottles/backend/managers/manager.py index 5f99fb6782..67ba635141 100644 --- a/bottles/backend/managers/manager.py +++ b/bottles/backend/managers/manager.py @@ -723,7 +723,8 @@ def get_programs(self, config: BottleConfig) -> List[dict]: "path": _program.get("path"), "folder": _program.get("folder", program_folder), "icon": "com.usebottles.bottles-program", - "script": _program.get("script"), + "pre_script": _program.get("pre_script"), + "post_script": _program.get("post_script"), "dxvk": _program.get("dxvk"), "vkd3d": _program.get("vkd3d"), "dxvk_nvapi": _program.get("dxvk_nvapi"), diff --git a/bottles/backend/wine/executor.py b/bottles/backend/wine/executor.py index a4c5eb61e1..b7d78a8b93 100644 --- a/bottles/backend/wine/executor.py +++ b/bottles/backend/wine/executor.py @@ -33,6 +33,7 @@ def __init__( environment: Optional[dict] = None, move_file: bool = False, move_upd_fn: callable = None, + pre_script: Optional[str] = None, post_script: Optional[str] = None, monitoring: Optional[list] = None, program_dxvk: Optional[bool] = None, @@ -60,6 +61,7 @@ def __init__( self.terminal = terminal self.cwd = self.__get_cwd(cwd) self.environment = environment + self.pre_script = pre_script self.post_script = post_script self.monitoring = monitoring self.use_virt_desktop = program_virt_desktop @@ -112,7 +114,8 @@ def run_program(cls, config: BottleConfig, program: dict, terminal: bool = False exec_path=program.get("path"), args=program.get("arguments"), cwd=program.get("folder"), - post_script=program.get("script"), + pre_script=program.get("pre_script"), + post_script=program.get("post_script"), terminal=terminal, program_dxvk=program.get("dxvk"), program_vkd3d=program.get("vkd3d"), @@ -264,6 +267,7 @@ def __launch_exe(self): cwd=self.cwd, environment=self.environment, communicate=True, + pre_script=self.pre_script, post_script=self.post_script, ) res = winecmd.run() @@ -300,6 +304,8 @@ def __launch_with_starter(self): terminal=self.terminal, args=self.args, environment=self.environment, + pre_script=self.pre_script, + post_script=self.post_script, cwd=self.cwd, ) self.__set_monitors() diff --git a/bottles/backend/wine/start.py b/bottles/backend/wine/start.py index a2c272c2e4..b88738ec11 100644 --- a/bottles/backend/wine/start.py +++ b/bottles/backend/wine/start.py @@ -17,6 +17,8 @@ def run( terminal: bool = True, args: str = "", environment: Optional[dict] = None, + pre_script: Optional[str] = None, + post_script: Optional[str] = None, cwd: Optional[str] = None, ): winepath = WinePath(self.config) @@ -37,6 +39,8 @@ def run( communicate=True, terminal=terminal, environment=environment, + pre_script=pre_script, + post_script=post_script, cwd=cwd, minimal=False, action_name="run", diff --git a/bottles/backend/wine/winecommand.py b/bottles/backend/wine/winecommand.py index 3bce1899e2..ef87fab35e 100644 --- a/bottles/backend/wine/winecommand.py +++ b/bottles/backend/wine/winecommand.py @@ -98,6 +98,7 @@ def __init__( cwd: Optional[str] = None, colors: str = "default", minimal: bool = False, # avoid gamemode/gamescope usage + pre_script: Optional[str] = None, post_script: Optional[str] = None, ): _environment = environment.copy() @@ -106,7 +107,7 @@ def __init__( self.arguments = arguments self.cwd = self._get_cwd(cwd) self.runner, self.runner_runtime = self._get_runner_info() - self.command = self.get_cmd(command, post_script, environment=_environment) + self.command = self.get_cmd(command, pre_script, post_script, environment=_environment) self.terminal = terminal self.env = self.get_env(_environment) self.communicate = communicate @@ -474,6 +475,7 @@ def _get_runner_info(self) -> tuple[str, str]: def get_cmd( self, command, + pre_script: Optional[str] = None, post_script: Optional[str] = None, return_steam_cmd: bool = False, return_clean_cmd: bool = False, @@ -588,6 +590,9 @@ def get_cmd( if post_script is not None: command = f"{command} ; sh '{post_script}'" + if pre_script is not None: + command = f"sh '{pre_script}' ; {command}" + return command def _get_gamescope_cmd(self, return_steam_cmd: bool = False) -> str: diff --git a/bottles/backend/wine/wineprogram.py b/bottles/backend/wine/wineprogram.py index 7f2b13c10f..2a848e0b68 100644 --- a/bottles/backend/wine/wineprogram.py +++ b/bottles/backend/wine/wineprogram.py @@ -43,6 +43,8 @@ def launch( minimal: bool = True, communicate: bool = False, environment: Optional[dict] = None, + pre_script: Optional[str] = None, + post_script: Optional[str] = None, cwd: Optional[str] = None, action_name: str = "launch", ): @@ -68,6 +70,8 @@ def launch( communicate=communicate, colors=self.colors, environment=environment, + pre_script=pre_script, + post_script=post_script, cwd=cwd, arguments=program_args, ).run() diff --git a/bottles/frontend/ui/dialog-launch-options.blp b/bottles/frontend/ui/dialog-launch-options.blp index 021e9bdab0..9f5792768b 100644 --- a/bottles/frontend/ui/dialog-launch-options.blp +++ b/bottles/frontend/ui/dialog-launch-options.blp @@ -47,15 +47,46 @@ template LaunchOptionsDialog : .AdwWindow { tooltip-text: _("e.g.: VAR=value %command% -example1 -example2 -example3=hello"); } - .AdwActionRow action_script { - activatable-widget: "btn_script"; + .AdwActionRow action_pre_script { + activatable-widget: "btn_pre_script"; + title: _("Pre-run Script"); + subtitle: _("Choose a script which should be executed before run."); + + Box { + spacing: 6; + + Button btn_pre_script_reset { + tooltip-text: _("Reset to Default"); + valign: center; + visible: false; + icon-name: "edit-undo-symbolic"; + + styles [ + "flat", + ] + } + + Button btn_pre_script { + tooltip-text: _("Choose a Script"); + valign: center; + icon-name: "document-open-symbolic"; + + styles [ + "flat", + ] + } + } + } + + .AdwActionRow action_post_script { + activatable-widget: "btn_post_script"; title: _("Post-run Script"); subtitle: _("Choose a script which should be executed after run."); Box { spacing: 6; - Button btn_script_reset { + Button btn_post_script_reset { tooltip-text: _("Reset to Default"); valign: center; visible: false; @@ -66,7 +97,7 @@ template LaunchOptionsDialog : .AdwWindow { ] } - Button btn_script { + Button btn_post_script { tooltip-text: _("Choose a Script"); valign: center; icon-name: "document-open-symbolic"; diff --git a/bottles/frontend/windows/launchoptions.py b/bottles/frontend/windows/launchoptions.py index 816f1d425c..adb122a323 100644 --- a/bottles/frontend/windows/launchoptions.py +++ b/bottles/frontend/windows/launchoptions.py @@ -18,8 +18,11 @@ from gi.repository import Gtk, GLib, GObject, Adw from bottles.backend.utils.manager import ManagerUtils +from bottles.backend.logger import Logger from gettext import gettext as _ +logging = Logger() + @Gtk.Template(resource_path="/com/usebottles/bottles/dialog-launch-options.ui") class LaunchOptionsDialog(Adw.Window): @@ -31,12 +34,15 @@ class LaunchOptionsDialog(Adw.Window): # region Widgets entry_arguments = Gtk.Template.Child() btn_save = Gtk.Template.Child() - btn_script = Gtk.Template.Child() - btn_script_reset = Gtk.Template.Child() + btn_pre_script = Gtk.Template.Child() + btn_pre_script_reset = Gtk.Template.Child() + btn_post_script = Gtk.Template.Child() + btn_post_script_reset = Gtk.Template.Child() btn_cwd = Gtk.Template.Child() btn_cwd_reset = Gtk.Template.Child() btn_reset_defaults = Gtk.Template.Child() - action_script = Gtk.Template.Child() + action_pre_script = Gtk.Template.Child() + action_post_script = Gtk.Template.Child() switch_dxvk = Gtk.Template.Child() switch_vkd3d = Gtk.Template.Child() switch_nvapi = Gtk.Template.Child() @@ -50,7 +56,8 @@ class LaunchOptionsDialog(Adw.Window): action_virt_desktop = Gtk.Template.Child() # endregion - __default_script_msg = _("Choose a script which should be executed after run.") + __default_pre_script_msg = _("Choose a script which should be executed before run.") + __default_post_script_msg = _("Choose a script which should be executed after run.") __default_cwd_msg = _("Choose from where start the program.") __msg_disabled = _("{0} is disabled globally for this bottle.") __msg_override = _("This setting overrides the bottle's global setting.") @@ -92,8 +99,10 @@ def __init__(self, parent, config, program, **kwargs): # connect signals self.btn_save.connect("clicked", self.__save) - self.btn_script.connect("clicked", self.__choose_script) - self.btn_script_reset.connect("clicked", self.__reset_script) + self.btn_pre_script.connect("clicked", self.__choose_pre_script) + self.btn_pre_script_reset.connect("clicked", self.__reset_pre_script) + self.btn_post_script.connect("clicked", self.__choose_post_script) + self.btn_post_script_reset.connect("clicked", self.__reset_post_script) self.btn_cwd.connect("clicked", self.__choose_cwd) self.btn_cwd_reset.connect("clicked", self.__reset_cwd) self.btn_reset_defaults.connect("clicked", self.__reset_defaults) @@ -149,9 +158,13 @@ def __init__(self, parent, config, program, **kwargs): "virtual_desktop", ) - if program.get("script") not in ["", None]: - self.action_script.set_subtitle(program["script"]) - self.btn_script_reset.set_visible(True) + if program.get("pre_script") not in ["", None]: + self.action_pre_script.set_subtitle(program["pre_script"]) + self.btn_pre_script_reset.set_visible(True) + + if program.get("post_script") not in ["", None]: + self.action_post_script.set_subtitle(program["post_script"]) + self.btn_post_script_reset.set_visible(True) if program.get("folder") not in [ "", @@ -207,31 +220,80 @@ def __idle_save(self, *_args): def __save(self, *_args): GLib.idle_add(self.__idle_save) - def __choose_script(self, *_args): - def set_path(dialog, response): - if response != Gtk.ResponseType.ACCEPT: - self.action_script.set_subtitle(self.__default_script_msg) - return - - file_path = dialog.get_file().get_path() - self.program["script"] = file_path - self.action_script.set_subtitle(file_path) - self.btn_script_reset.set_visible(True) - - dialog = Gtk.FileChooserNative.new( - title=_("Select Script"), - parent=self.window, - action=Gtk.FileChooserAction.OPEN, - ) - + def __choose_pre_script(self, *_args): + def set_path(dialog, result): + + try: + file = dialog.open_finish(result) + + if file is None: + self.action_pre_script.set_subtitle( + self.__default_pre_script_msg) + return + + file_path = file.get_path() + + self.program["pre_script"] = file_path + self.action_pre_script.set_subtitle(file_path) + self.btn_pre_script_reset.set_visible(True) + + except GLib.Error as error: + # also thrown when dialog has been cancelled + if error.code == 2: + # error 2 seems to be 'dismiss' or 'cancel' + if self.program["pre_script"] is None or self.program["pre_script"] == "": + self.action_pre_script.set_subtitle( + self.__default_pre_script_msg) + else: + # something else happened... + logging.warning("Error selecting pre-run script: %s" % error) + pass + + dialog = Gtk.FileDialog.new() + dialog.set_title("Select Pre-run Script") dialog.set_modal(True) - dialog.connect("response", set_path) - dialog.show() + dialog.open(parent=self.window, callback=set_path) + + def __choose_post_script(self, *_args): + def set_path(dialog, result): + + try: + file = dialog.open_finish(result) + + if file is None: + self.action_post_script.set_subtitle( + self.__default_post_script_msg) + return + + file_path = file.get_path() + self.program["post_script"] = file_path + self.action_post_script.set_subtitle(file_path) + self.btn_post_script_reset.set_visible(True) + except GLib.Error as error: + # also thrown when dialog has been cancelled + if error.code == 2: + # error 2 seems to be 'dismiss' or 'cancel' + if self.program["post_script"] is None or self.program["post_script"] == "": + self.action_pre_script.set_subtitle( + self.__default_pre_script_msg) + else: + # something else happened... + logging.warning("Error selecting post-run script: %s" % error) + + dialog = Gtk.FileDialog.new() + dialog.set_title("Select Post-run Script") + dialog.set_modal(True) + dialog.open(parent=self.window, callback=set_path) + + def __reset_pre_script(self, *_args): + self.program["pre_script"] = "" + self.action_pre_script.set_subtitle(self.__default_pre_script_msg) + self.btn_pre_script_reset.set_visible(False) - def __reset_script(self, *_args): - self.program["script"] = "" - self.action_script.set_subtitle(self.__default_script_msg) - self.btn_script_reset.set_visible(False) + def __reset_post_script(self, *_args): + self.program["post_script"] = "" + self.action_post_script.set_subtitle(self.__default_post_script_msg) + self.btn_post_script_reset.set_visible(False) def __choose_cwd(self, *_args): def set_path(dialog, response):