From 767d8774427ee869bf3157f5c010b48854a9af92 Mon Sep 17 00:00:00 2001 From: Mathieu Broillet Date: Fri, 30 Aug 2024 09:55:51 +0200 Subject: [PATCH] implement change audio config service --- .../computer/__init__.py | 17 +++++--- .../computer/formatter.py | 40 +++++++++++++++++++ .../easy_computer_manager/computer/utils.py | 6 +-- .../easy_computer_manager/const.py | 6 +++ 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/custom_components/easy_computer_manager/computer/__init__.py b/custom_components/easy_computer_manager/computer/__init__.py index 7e01790..b6d4c59 100644 --- a/custom_components/easy_computer_manager/computer/__init__.py +++ b/custom_components/easy_computer_manager/computer/__init__.py @@ -7,7 +7,7 @@ from wakeonlan import send_magic_packet from custom_components.easy_computer_manager import const, LOGGER from custom_components.easy_computer_manager.computer.common import OSType, CommandOutput -from custom_components.easy_computer_manager.computer.formatter import format_gnome_monitors_args +from custom_components.easy_computer_manager.computer.formatter import format_gnome_monitors_args, format_pactl_commands from custom_components.easy_computer_manager.computer.parser import parse_gnome_monitors_output, parse_pactl_output, \ parse_bluetoothctl @@ -35,7 +35,7 @@ class Computer: asyncio.create_task(self.update()) - async def _connect(self) -> None: + async def _connect(self, retried: bool = False) -> None: """Open an asynchronous SSH connection.""" try: client = await asyncssh.connect( @@ -48,7 +48,10 @@ class Computer: asyncssh.set_log_level("ERROR") self._connection = client except (OSError, asyncssh.Error) as exc: - raise ValueError(f"Failed to connect to {self.host}: {exc}") + if retried: + await self._connect(retried=True) + else: + raise ValueError(f"Failed to connect to {self.host}: {exc}") async def _renew_connection(self) -> None: """Renew the SSH connection if it is closed.""" @@ -159,8 +162,12 @@ class Computer: input_device: str | None = None, output_device: str | None = None) -> None: """Change the audio configuration.""" - # Implementation needed - pass + if self.is_linux(): + # TODO: other DE support + if self.desktop_environment == 'gnome': + pactl_commands = format_pactl_commands(self.audio_config, volume, mute, input_device, output_device) + for command in pactl_commands: + await self.run_action("set_audio_config", params={"args": command}) async def install_nircmd(self) -> None: """Install NirCmd tool (Windows specific).""" diff --git a/custom_components/easy_computer_manager/computer/formatter.py b/custom_components/easy_computer_manager/computer/formatter.py index 2e0c559..f1f0bcb 100644 --- a/custom_components/easy_computer_manager/computer/formatter.py +++ b/custom_components/easy_computer_manager/computer/formatter.py @@ -20,3 +20,43 @@ def format_gnome_monitors_args(monitors_config: dict): args.extend(['-t', settings["transform"]]) return ' '.join(args) + + +def format_pactl_commands(current_config: {}, volume: int, mute: bool, input_device: str = "@DEFAULT_SOURCE@", + output_device: str = "@DEFAULT_SINK@"): + """Change audio configuration on the host system.""" + + commands = [] + + def get_device_id(device_type, user_device): + for device in current_config[device_type]: + if device['description'] == user_device: + return device['name'] + return user_device + + # Set default sink and source if not specified + if not output_device: + output_device = "@DEFAULT_SINK@" + if not input_device: + input_device = "@DEFAULT_SOURCE@" + + # Set default sink if specified + if output_device and output_device != "@DEFAULT_SINK@": + output_device = get_device_id('sinks', output_device) + commands.append(f"set-default-sink {output_device}") + + # Set default source if specified + if input_device and input_device != "@DEFAULT_SOURCE@": + input_device = get_device_id('sources', input_device) + commands.append(f"set-default-source {input_device}") + + # Set sink volume if specified + if volume is not None: + commands.append(f"set-sink-volume {output_device} {volume}%") + + # Set sink and source mute status if specified + if mute is not None: + commands.append(f"set-sink-mute {output_device} {'yes' if mute else 'no'}") + commands.append(f"set-source-mute {input_device} {'yes' if mute else 'no'}") + + return commands diff --git a/custom_components/easy_computer_manager/computer/utils.py b/custom_components/easy_computer_manager/computer/utils.py index 28c14bd..f8d22b0 100644 --- a/custom_components/easy_computer_manager/computer/utils.py +++ b/custom_components/easy_computer_manager/computer/utils.py @@ -7,7 +7,7 @@ async def format_debug_information(computer: 'Computer'): # importing Computer 'version': computer.operating_system_version, 'desktop_environment': computer.desktop_environment }, - 'connection':{ + 'connection': { 'host': computer.host, 'mac': computer.mac, 'username': computer.username, @@ -15,10 +15,10 @@ async def format_debug_information(computer: 'Computer'): # importing Computer 'dualboot': computer.dualboot, 'is_on': await computer.is_on() }, - 'grub':{ + 'grub': { 'windows_entry': computer.windows_entry_grub }, - 'audio':{ + 'audio': { 'speakers': computer.audio_config.get('speakers'), 'microphones': computer.audio_config.get('microphones') }, diff --git a/custom_components/easy_computer_manager/const.py b/custom_components/easy_computer_manager/const.py index f7e8691..929c4b2 100644 --- a/custom_components/easy_computer_manager/const.py +++ b/custom_components/easy_computer_manager/const.py @@ -64,6 +64,12 @@ ACTIONS = { "get_microphones": { "linux": ["LANG=en_US.UTF-8 pactl list sources"] }, + "set_audio_config": { + "linux": { + "command": "LANG=en_US.UTF-8 pactl %args%", + "params": ["args"] + } + }, "get_bluetooth_devices": { "linux": { "command": "bluetoothctl info",