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

Add audio support #141

Draft
wants to merge 6 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions exegol/console/cli/actions/GenericParameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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},
Expand Down
2 changes: 2 additions & 0 deletions exegol/manager/ExegolManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
43 changes: 42 additions & 1 deletion exegol/model/ContainerConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -32,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"}
Expand All @@ -41,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"
Expand Down Expand Up @@ -228,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):
Expand Down Expand Up @@ -296,6 +309,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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why must_exist is set to False here ? Shouldn't be True instead ?
If the socket is not present, the feature cannot be activated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just below, he left a commented FIXME, indicating that a solution must be found here. In it's current state and in this context, the addVolume function raises an error when must_exist is True 😉

# 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")
TahiTi marked this conversation as resolved.
Show resolved Hide resolved
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():
Expand All @@ -308,7 +350,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)
Expand Down
48 changes: 48 additions & 0 deletions exegol/utils/SoundUtils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import io
import os
import shutil
import subprocess
import time
TahiTi marked this conversation as resolved.
Show resolved Hide resolved
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 = ""
TahiTi marked this conversation as resolved.
Show resolved Hide resolved

@classmethod
def isPulseAudioAvailable(cls) -> bool:
"""
Check if the host OS has PulseAudio installed
:return: bool
"""
assert os.getenv("XDG_RUNTIME_DIR")
Dramelac marked this conversation as resolved.
Show resolved Hide resolved
return Path(f"{os.getenv('XDG_RUNTIME_DIR')}/pulse/native").exists()

@classmethod
def getPulseAudioSocketPath(cls) -> str:
"""
Get the host path of the Pulse Audio socket
:return:
"""
# todo : find the path for windows/WSL
assert os.getenv("XDG_RUNTIME_DIR")
Dramelac marked this conversation as resolved.
Show resolved Hide resolved
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"
Dramelac marked this conversation as resolved.
Show resolved Hide resolved