Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-mount disc images (CD/DVD) on program launch #3651

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions bottles/backend/managers/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@ def get_programs(self, config: BottleConfig) -> List[dict]:
"pre_script": _program.get("pre_script"),
"post_script": _program.get("post_script"),
"folder": _program.get("folder", program_folder),
"disc_image": _program.get("disc_image"),
"dxvk": _program.get("dxvk"),
"vkd3d": _program.get("vkd3d"),
"dxvk_nvapi": _program.get("dxvk_nvapi"),
Expand Down
2 changes: 1 addition & 1 deletion bottles/backend/wine/drives.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def get_drive(self, letter: str):
return None

def set_drive_path(self, letter: str, path: str):
"""Change a drives path in the bottle"""
"""Change a drive's path in the bottle"""
letter = f"{letter}:".lower()
drive_sym_path = os.path.join(self.dosdevices_path, letter)
if not os.path.exists(self.dosdevices_path):
Expand Down
6 changes: 6 additions & 0 deletions bottles/backend/wine/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def __init__(
pre_script: Optional[str] = None,
post_script: Optional[str] = None,
cwd: Optional[str] = None,
disc_image: Optional[str] = None,
monitoring: Optional[list] = None,
program_dxvk: Optional[bool] = None,
program_vkd3d: Optional[bool] = None,
Expand Down Expand Up @@ -63,6 +64,7 @@ def __init__(
self.pre_script = pre_script
self.post_script = post_script
self.cwd = self.__get_cwd(cwd)
self.disc_image = disc_image
self.monitoring = monitoring
self.use_gamescope = program_gamescope
self.use_virt_desktop = program_virt_desktop
Expand Down Expand Up @@ -123,6 +125,7 @@ def run_program(cls, config: BottleConfig, program: dict, terminal: bool = False
pre_script=program.get("pre_script"),
post_script=program.get("post_script"),
cwd=program.get("folder"),
disc_image=program.get("disc_image"),
terminal=terminal,
program_dxvk=program.get("dxvk"),
program_vkd3d=program.get("vkd3d"),
Expand Down Expand Up @@ -213,6 +216,7 @@ def run_cli(self):
pre_script=self.pre_script,
post_script=self.post_script,
cwd=self.cwd,
disc_image=self.disc_image,
)
return Result(status=True, data={"output": res})

Expand Down Expand Up @@ -279,6 +283,7 @@ def __launch_exe(self):
pre_script=self.pre_script,
post_script=self.post_script,
cwd=self.cwd,
disc_image=self.disc_image,
)
res = winecmd.run()
self.__set_monitors()
Expand Down Expand Up @@ -317,6 +322,7 @@ def __launch_with_starter(self):
pre_script=self.pre_script,
post_script=self.post_script,
cwd=self.cwd,
disc_image=self.disc_image,
)
self.__set_monitors()
return Result(status=True, data={"output": res})
Expand Down
2 changes: 2 additions & 0 deletions bottles/backend/wine/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def run(
pre_script: Optional[str] = None,
post_script: Optional[str] = None,
cwd: Optional[str] = None,
disc_image: Optional[str] = None,
):
winepath = WinePath(self.config)

Expand All @@ -42,6 +43,7 @@ def run(
pre_script=pre_script,
post_script=post_script,
cwd=cwd,
disc_image=disc_image,
minimal=False,
action_name="run",
)
22 changes: 21 additions & 1 deletion bottles/backend/wine/winecommand.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import shutil
import stat
import string
import subprocess
import tempfile
import shlex
Expand All @@ -25,6 +26,7 @@
from bottles.backend.utils.manager import ManagerUtils
from bottles.backend.utils.terminal import TerminalUtils
from bottles.backend.utils.steam import SteamUtils
from bottles.backend.wine.drives import Drives

logging = Logger()

Expand Down Expand Up @@ -100,6 +102,7 @@ def __init__(
pre_script: Optional[str] = None,
post_script: Optional[str] = None,
cwd: Optional[str] = None,
disc_image: Optional[str] = None,
):
_environment = environment.copy()
self.config = self._get_config(config)
Expand All @@ -113,7 +116,7 @@ def __init__(
else self.config.Parameters.gamescope
)
self.command = self.get_cmd(
command, pre_script, post_script, environment=_environment
command, pre_script, post_script, disc_image, environment=_environment
)
self.terminal = terminal
self.env = self.get_env(_environment)
Expand Down Expand Up @@ -489,6 +492,7 @@ def get_cmd(
command,
pre_script: Optional[str] = None,
post_script: Optional[str] = None,
disc_image: Optional[str] = None,
return_steam_cmd: bool = False,
return_clean_cmd: bool = False,
environment: Optional[dict] = None,
Expand Down Expand Up @@ -603,6 +607,22 @@ def get_cmd(
if pre_script not in (None, ""):
command = f"sh '{pre_script}' ; {command}"

if disc_image is not None:
# Mount/unmount disc image on temp mount point
mount_point = "/tmp/bottles/disc"
command = f"""
flatpak-spawn --host mkdir -p '{mount_point}'
flatpak-spawn --host fuseiso '{disc_image}' '{mount_point}'
{command}
flatpak-spawn --host fusermount -uz '{mount_point}' # -z for lazy unmount
"""

# Assign path to first free drive letter
drives = Drives(self.config)
alphabet = string.ascii_uppercase
letter = next(c for c in alphabet if c >= "D" and not drives.get_drive(c))
drives.set_drive_path(letter, mount_point)

return command

def _get_gamescope_cmd(self, return_steam_cmd: bool = False) -> str:
Expand Down
2 changes: 2 additions & 0 deletions bottles/backend/wine/wineprogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def launch(
pre_script: Optional[str] = None,
post_script: Optional[str] = None,
cwd: Optional[str] = None,
disc_image: Optional[str] = None,
action_name: str = "launch",
):
if environment is None:
Expand Down Expand Up @@ -73,6 +74,7 @@ def launch(
pre_script=pre_script,
post_script=post_script,
cwd=cwd,
disc_image=disc_image,
arguments=program_args,
)

Expand Down
1 change: 1 addition & 0 deletions bottles/frontend/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ def run_program(self):
program.get("pre_script", None)
program.get("post_script", None)
program.get("folder", None)
program.get("disc_image", None)

program.get("dxvk")
program.get("vkd3d")
Expand Down
31 changes: 31 additions & 0 deletions bottles/frontend/ui/dialog-launch-options.blp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,37 @@ template $LaunchOptionsDialog: Adw.Window {
}
}
}

Adw.ActionRow action_disc_image {
activatable-widget: btn_disc_image;
title: _("CD/DVD Image");
subtitle: _("Choose an optical disc image to be mounted.");

Box {
spacing: 6;

Button btn_disc_image_reset {
tooltip-text: _("Reset to Default");
valign: center;
visible: false;
icon-name: "edit-undo-symbolic";

styles [
"flat",
]
}

Button btn_disc_image {
tooltip-text: _("Choose a Disc Image");
valign: center;
icon-name: "document-open-symbolic";

styles [
"flat",
]
}
}
}
}

Adw.PreferencesGroup {
Expand Down
12 changes: 12 additions & 0 deletions bottles/frontend/utils/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ def add_yaml_filters(dialog):
dialog.add_filter(filter)


def add_disc_image_filters(dialog):
filter = Gtk.FileFilter()
filter.set_name(_("Optical Disc Image"))
filter.add_pattern("*.bin")
filter.add_pattern("*.cue")
filter.add_pattern("*.img")
filter.add_pattern("*.iso")
filter.add_pattern("*.mds")

dialog.add_filter(filter)


def add_all_filters(dialog):
filter = Gtk.FileFilter()
filter.set_name(_("All Files"))
Expand Down
42 changes: 41 additions & 1 deletion bottles/frontend/windows/launchoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from bottles.backend.utils.manager import ManagerUtils
from bottles.backend.logger import Logger
from bottles.frontend.utils.filters import add_disc_image_filters, add_all_filters
from gettext import gettext as _

logging = Logger()
Expand All @@ -40,9 +41,13 @@ class LaunchOptionsDialog(Adw.Window):
btn_post_script_reset = Gtk.Template.Child()
btn_cwd = Gtk.Template.Child()
btn_cwd_reset = Gtk.Template.Child()
btn_disc_image = Gtk.Template.Child()
btn_disc_image_reset = Gtk.Template.Child()
btn_reset_defaults = Gtk.Template.Child()
action_pre_script = Gtk.Template.Child()
action_post_script = Gtk.Template.Child()
action_cwd = Gtk.Template.Child()
action_disc_image = Gtk.Template.Child()
switch_dxvk = Gtk.Template.Child()
switch_vkd3d = Gtk.Template.Child()
switch_nvapi = Gtk.Template.Child()
Expand All @@ -54,13 +59,13 @@ class LaunchOptionsDialog(Adw.Window):
action_nvapi = Gtk.Template.Child()
action_fsr = Gtk.Template.Child()
action_gamescope = Gtk.Template.Child()
action_cwd = Gtk.Template.Child()
action_virt_desktop = Gtk.Template.Child()
# endregion

__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.")
__default_disc_image_msg = _("Choose an optical disc image to be mounted.")
__msg_disabled = _("{0} is disabled globally for this bottle.")
__msg_override = _("This setting overrides the bottle's global setting.")

Expand Down Expand Up @@ -108,6 +113,8 @@ def __init__(self, parent, config, program, **kwargs):
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_disc_image.connect("clicked", self.__choose_disc_image)
self.btn_disc_image_reset.connect("clicked", self.__reset_disc_image)
self.btn_reset_defaults.connect("clicked", self.__reset_defaults)
self.entry_arguments.connect("activate", self.__save)

Expand Down Expand Up @@ -185,6 +192,10 @@ def __init__(self, parent, config, program, **kwargs):
self.action_cwd.set_subtitle(program["folder"])
self.btn_cwd_reset.set_visible(True)

if program.get("disc_image") not in ["", None]:
self.action_disc_image.set_subtitle(program["disc_image"])
self.btn_disc_image_reset.set_visible(True)

self.__set_disabled_switches()

def __check_override(self, widget, state, action, name):
Expand Down Expand Up @@ -344,6 +355,35 @@ def __reset_cwd(self, *_args):
self.action_cwd.set_subtitle(self.__default_cwd_msg)
self.btn_cwd_reset.set_visible(False)

def __choose_disc_image(self, *_args):
def set_path(dialog, response):
if response != Gtk.ResponseType.ACCEPT:
self.action_disc_image.set_subtitle(self.__default_disc_image_msg)
return

disc_image = dialog.get_file().get_path()
self.program["disc_image"] = disc_image
self.action_disc_image.set_subtitle(disc_image)
self.btn_disc_image_reset.set_visible(True)

dialog = Gtk.FileChooserNative.new(
title=_("Select a CD/DVD Image"),
action=Gtk.FileChooserAction.OPEN,
parent=self.window,
accept_label=_("Select"),
)

add_disc_image_filters(dialog)
add_all_filters(dialog)
dialog.set_modal(True)
dialog.connect("response", set_path)
dialog.show()

def __reset_disc_image(self, *_args):
self.program["disc_image"] = None
self.action_disc_image.set_subtitle(self.__default_disc_image_msg)
self.btn_disc_image_reset.set_visible(False)

def __reset_defaults(self, *_args):
self.switch_dxvk.set_active(self.global_dxvk)
self.switch_vkd3d.set_active(self.global_vkd3d)
Expand Down
Loading