From f42a48fc7e3a456eab71e20fd8e026b1b7194b82 Mon Sep 17 00:00:00 2001 From: TahiTi Date: Thu, 23 Feb 2023 15:33:58 +0100 Subject: [PATCH 1/4] Add audio support --- exegol/model/ContainerConfig.py | 31 ++++++++++++++++++++- exegol/utils/SoundUtils.py | 48 +++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 exegol/utils/SoundUtils.py diff --git a/exegol/model/ContainerConfig.py b/exegol/model/ContainerConfig.py index d644b3ad..8f625b95 100644 --- a/exegol/model/ContainerConfig.py +++ b/exegol/model/ContainerConfig.py @@ -18,6 +18,7 @@ from exegol.utils.EnvInfo import EnvInfo from exegol.utils.ExeLog import logger, ExeLog from exegol.utils.GuiUtils import GuiUtils +from exegol.utils.SoundUtils import SoundUtils from exegol.utils.UserConfig import UserConfig @@ -296,6 +297,35 @@ def interactiveConfig(self, container_name: str) -> List[str]: return command_options + def enableSound(self): + """Procedure to enable sound feature""" + if not SoundUtils.isPulseAudioAvailable(): + logger.error("Sound sharing feature is [red]not available[/red] on your environment. [orange3]Skipping[/orange3].") + return + if not self.__enable_sound: + logger.verbose("Config: Enabling sound sharing") + try: + self.addVolume(SoundUtils.getPulseAudioSocketPath(), "/run/user/0/pulse/native", must_exist=False) + # fixme must_exist cannot be set to True since addVolume will fail, this needs to be fixed + self.addVolume(SoundUtils.getPulseAudioCookiePath(), "/root/.config/pulse/cookie", must_exist=True) + except CancelOperation as e: + logger.warning(f"Sound socket sharing could not be enabled: {e}") + return + for k, v in self.__static_pulseaudio_envs.items(): + self.addEnv(k, v) + self.__enable_sound = True + + + def __disableSound(self): + """Procedure to enable GUI feature (Only for interactive config)""" + if self.__enable_sound: + self.__enable_sound = False + logger.verbose("Config: Sound sharing") + self.removeVolume(container_path="/run/user/0/pulse/native") + self.removeVolume(container_path="/root/.config/pulse/cookie") + for k in self.__static_gui_envs.keys(): + self.removeEnv(k) + def enableGUI(self): """Procedure to enable GUI feature""" if not GuiUtils.isGuiAvailable(): @@ -308,7 +338,6 @@ def enableGUI(self): except CancelOperation as e: logger.warning(f"Graphical interface sharing could not be enabled: {e}") return - # TODO support pulseaudio self.addEnv("DISPLAY", GuiUtils.getDisplayEnv()) for k, v in self.__static_gui_envs.items(): self.addEnv(k, v) diff --git a/exegol/utils/SoundUtils.py b/exegol/utils/SoundUtils.py new file mode 100644 index 00000000..3e8c7e33 --- /dev/null +++ b/exegol/utils/SoundUtils.py @@ -0,0 +1,48 @@ +import io +import os +import shutil +import subprocess +import time +from pathlib import Path +from typing import Optional + +from exegol.console.ExegolPrompt import Confirm +from exegol.exceptions.ExegolExceptions import CancelOperation +from exegol.utils.EnvInfo import EnvInfo +from exegol.utils.ExeLog import logger, console + + +class SoundUtils: + """This utility class allows determining if the current system supports the sound sharing + from the information of the system.""" + + __distro_name = "" + + @classmethod + def isPulseAudioAvailable(cls) -> bool: + """ + Check if the host OS has PulseAudio installed + :return: bool + """ + assert os.getenv("XDG_RUNTIME_DIR") + return Path(f"{os.getenv('XDG_RUNTIME_DIR')}/pulse/native").exists() + + @classmethod + def getPulseAudioSocketPath(cls) -> Path: + """ + Get the host path of the Pulse Audio socket + :return: + """ + # todo : find the path for windows/WSL + assert os.getenv("XDG_RUNTIME_DIR") + return f"{os.getenv('XDG_RUNTIME_DIR')}/pulse/native" + + @classmethod + def getPulseAudioCookiePath(cls) -> str: + """ + Get the host path of the Pulse Audio cookie + :return: + """ + # todo : find the path for windows/WSL + return f"{os.getenv('HOME')}/.config/pulse/cookie" + #return Path().home() / "/.config/pulse/cookie" From c3891fad9e0c392cef2d86e542ca4e1678dafde6 Mon Sep 17 00:00:00 2001 From: TahiTi Date: Thu, 23 Feb 2023 15:43:52 +0100 Subject: [PATCH 2/4] Fixed return type getPulseAudioSocketPath --- exegol/utils/SoundUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exegol/utils/SoundUtils.py b/exegol/utils/SoundUtils.py index 3e8c7e33..0ef5c4a5 100644 --- a/exegol/utils/SoundUtils.py +++ b/exegol/utils/SoundUtils.py @@ -28,7 +28,7 @@ def isPulseAudioAvailable(cls) -> bool: return Path(f"{os.getenv('XDG_RUNTIME_DIR')}/pulse/native").exists() @classmethod - def getPulseAudioSocketPath(cls) -> Path: + def getPulseAudioSocketPath(cls) -> str: """ Get the host path of the Pulse Audio socket :return: From 21ce11c28de8a40f809208669754caa36bdf04fc Mon Sep 17 00:00:00 2001 From: TahiTi Date: Thu, 23 Feb 2023 15:46:23 +0100 Subject: [PATCH 3/4] Changes by charlie --- exegol/console/cli/actions/GenericParameters.py | 6 ++++++ exegol/manager/ExegolManager.py | 2 ++ exegol/model/ContainerConfig.py | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/exegol/console/cli/actions/GenericParameters.py b/exegol/console/cli/actions/GenericParameters.py index 23c300fd..ee0bd1b7 100644 --- a/exegol/console/cli/actions/GenericParameters.py +++ b/exegol/console/cli/actions/GenericParameters.py @@ -100,6 +100,11 @@ def __init__(self, groupArgs: List[GroupArg]): default=True, dest="X11", help="Disable display sharing to run GUI-based applications (default: [green]Enabled[/green])") + self.sound_sharing = Option("--sound", + action="store_true", + default=False, + dest="sound_sharing", + help="Enable sound sharing") self.my_resources = Option("--disable-my-resources", action="store_false", default=True, @@ -191,6 +196,7 @@ def __init__(self, groupArgs: List[GroupArg]): {"arg": self.capabilities, "required": False}, {"arg": self.privileged, "required": False}, {"arg": self.devices, "required": False}, + {"arg": self.sound_sharing, "required": False}, {"arg": self.X11, "required": False}, {"arg": self.my_resources, "required": False}, {"arg": self.exegol_resources, "required": False}, diff --git a/exegol/manager/ExegolManager.py b/exegol/manager/ExegolManager.py index 58786451..97679310 100644 --- a/exegol/manager/ExegolManager.py +++ b/exegol/manager/ExegolManager.py @@ -435,6 +435,8 @@ def __prepareContainerConfig(cls): # Container configuration from user CLI options if ParametersManager().X11: config.enableGUI() + if ParametersManager().sound_sharing: + config.enableSound() if ParametersManager().share_timezone: config.enableSharedTimezone() config.setNetworkMode(ParametersManager().host_network) diff --git a/exegol/model/ContainerConfig.py b/exegol/model/ContainerConfig.py index d73cb90d..96274c22 100644 --- a/exegol/model/ContainerConfig.py +++ b/exegol/model/ContainerConfig.py @@ -33,6 +33,7 @@ class ContainerConfig: # Reference static config data __static_gui_envs = {"_JAVA_AWT_WM_NONREPARENTING": "1", "QT_X11_NO_MITSHM": "1"} + __static_pulseaudio_envs = {"PULSE_SERVER": "unix:/run/user/0/pulse/native"} # Label features (wrapper method to enable the feature / label name) __label_features = {"enableShellLogging": "org.exegol.feature.shell_logging"} @@ -42,6 +43,7 @@ class ContainerConfig: def __init__(self, container: Optional[Container] = None): """Container config default value""" self.__enable_gui: bool = False + self.__enable_sound: bool = False self.__share_timezone: bool = False self.__my_resources: bool = False self.__my_resources_path: str = "/opt/my-resources" @@ -229,6 +231,16 @@ def interactiveConfig(self, container_name: str) -> List[str]: if not self.__enable_gui: command_options.append("--disable-X11") + # Sound sharing + if self.__enable_sound: + if Confirm("Do you want to [orange3]disable[/orange3] [blue]sound sharing[/blue]?", False): + self.__disableSound() + elif Confirm("Do you want to [green]enable[/green] [blue]sound sharing[/blue]?", False): + self.enableSound() + # Command builder info + if self.__enable_sound: + command_options.append("--sound") + # Timezone config if self.__share_timezone: if Confirm("Do you want to [orange3]remove[/orange3] your [blue]shared timezone[/blue] config?", False): From 9841284adf792b7d59d4dce2897dd591a9d4b058 Mon Sep 17 00:00:00 2001 From: TahiTi Date: Fri, 24 Feb 2023 11:05:16 +0100 Subject: [PATCH 4/4] Fixed Dramelac comments --- exegol/model/ContainerConfig.py | 4 ++-- exegol/utils/SoundUtils.py | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/exegol/model/ContainerConfig.py b/exegol/model/ContainerConfig.py index 96274c22..8f9cde6b 100644 --- a/exegol/model/ContainerConfig.py +++ b/exegol/model/ContainerConfig.py @@ -329,10 +329,10 @@ def enableSound(self): def __disableSound(self): - """Procedure to enable GUI feature (Only for interactive config)""" + """Procedure to disable sound feature (Only for interactive config)""" if self.__enable_sound: self.__enable_sound = False - logger.verbose("Config: Sound sharing") + logger.verbose("Config: Disabling sound sharing") self.removeVolume(container_path="/run/user/0/pulse/native") self.removeVolume(container_path="/root/.config/pulse/cookie") for k in self.__static_gui_envs.keys(): diff --git a/exegol/utils/SoundUtils.py b/exegol/utils/SoundUtils.py index 0ef5c4a5..bfa0be45 100644 --- a/exegol/utils/SoundUtils.py +++ b/exegol/utils/SoundUtils.py @@ -1,8 +1,5 @@ import io import os -import shutil -import subprocess -import time from pathlib import Path from typing import Optional @@ -16,16 +13,16 @@ class SoundUtils: """This utility class allows determining if the current system supports the sound sharing from the information of the system.""" - __distro_name = "" - @classmethod def isPulseAudioAvailable(cls) -> bool: """ Check if the host OS has PulseAudio installed :return: bool """ - assert os.getenv("XDG_RUNTIME_DIR") - return Path(f"{os.getenv('XDG_RUNTIME_DIR')}/pulse/native").exists() + if "XDG_RUNTIME_DIR" in os.environ: + return Path(f"{os.getenv('XDG_RUNTIME_DIR')}/pulse/native").exists() + else: + return Path(f"/run/user/{os.getuid()}/pulse/native").exists() @classmethod def getPulseAudioSocketPath(cls) -> str: @@ -34,8 +31,10 @@ def getPulseAudioSocketPath(cls) -> str: :return: """ # todo : find the path for windows/WSL - assert os.getenv("XDG_RUNTIME_DIR") - return f"{os.getenv('XDG_RUNTIME_DIR')}/pulse/native" + if "XDG_RUNTIME_DIR" in os.environ: + return f"{os.getenv('XDG_RUNTIME_DIR')}/pulse/native" + else: + return f"/run/user/{os.getuid()}/pulse/native" @classmethod def getPulseAudioCookiePath(cls) -> str: @@ -44,5 +43,5 @@ def getPulseAudioCookiePath(cls) -> str: :return: """ # todo : find the path for windows/WSL - return f"{os.getenv('HOME')}/.config/pulse/cookie" - #return Path().home() / "/.config/pulse/cookie" + # return f"{os.getenv('HOME')}/.config/pulse/cookie" + return str(Path().home() / ".config/pulse/cookie")