From 32572ea5a177490bce26e96b0f33644f24656c37 Mon Sep 17 00:00:00 2001 From: Mathieu Broillet Date: Thu, 28 Dec 2023 22:40:29 +0100 Subject: [PATCH] Add option to change the monitors config on the host --- .../easy_computer_manage/__init__.py | 2 +- .../easy_computer_manage/const.py | 3 +- .../easy_computer_manage/services.yaml | 13 ++++ .../easy_computer_manage/switch.py | 16 +++- .../easy_computer_manage/utils.py | 73 +++++++++++++++++++ 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/custom_components/easy_computer_manage/__init__.py b/custom_components/easy_computer_manage/__init__.py index 0fec1e7..37f56e7 100644 --- a/custom_components/easy_computer_manage/__init__.py +++ b/custom_components/easy_computer_manage/__init__.py @@ -14,7 +14,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_BROADCAST_ADDRESS, CONF_BROADCAST_PORT, CONF_MAC from homeassistant.core import HomeAssistant, ServiceCall -from .const import DOMAIN, SERVICE_SEND_MAGIC_PACKET +from .const import DOMAIN, SERVICE_SEND_MAGIC_PACKET, SERVICE_CHANGE_MONITORS_CONFIG _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/easy_computer_manage/const.py b/custom_components/easy_computer_manage/const.py index cab20c7..8b8d9bb 100644 --- a/custom_components/easy_computer_manage/const.py +++ b/custom_components/easy_computer_manage/const.py @@ -6,4 +6,5 @@ SERVICE_RESTART_TO_WINDOWS_FROM_LINUX = "restart_to_windows_from_linux" SERVICE_RESTART_TO_LINUX_FROM_WINDOWS = "restart_to_linux_from_windows" SERVICE_PUT_COMPUTER_TO_SLEEP = "put_computer_to_sleep" SERVICE_START_COMPUTER_TO_WINDOWS = "start_computer_to_windows" -RESTART_COMPUTER = "restart_computer" +SERVICE_RESTART_COMPUTER = "restart_computer" +SERVICE_CHANGE_MONITORS_CONFIG = "change_monitors_config" diff --git a/custom_components/easy_computer_manage/services.yaml b/custom_components/easy_computer_manage/services.yaml index 0e4cc02..d435823 100644 --- a/custom_components/easy_computer_manage/services.yaml +++ b/custom_components/easy_computer_manage/services.yaml @@ -50,6 +50,19 @@ put_computer_to_sleep: restart_computer: name: Restart description: Restart the computer. + target: + device: + integration: easy_computer_manage +change_monitors_config: + name: Change monitors config + description: Change monitors config. + fields: + monitors_config: + name: Monitors config + description: Monitors config. + required: true + selector: + text: target: device: integration: easy_computer_manage \ No newline at end of file diff --git a/custom_components/easy_computer_manage/switch.py b/custom_components/easy_computer_manage/switch.py index 3379ec0..1aa7af1 100644 --- a/custom_components/easy_computer_manage/switch.py +++ b/custom_components/easy_computer_manage/switch.py @@ -35,7 +35,8 @@ from paramiko.ssh_exception import AuthenticationException from . import utils from .const import SERVICE_RESTART_TO_WINDOWS_FROM_LINUX, SERVICE_PUT_COMPUTER_TO_SLEEP, \ - SERVICE_START_COMPUTER_TO_WINDOWS, RESTART_COMPUTER, SERVICE_RESTART_TO_LINUX_FROM_WINDOWS + SERVICE_START_COMPUTER_TO_WINDOWS, SERVICE_RESTART_COMPUTER, SERVICE_RESTART_TO_LINUX_FROM_WINDOWS, \ + SERVICE_CHANGE_MONITORS_CONFIG _LOGGER = logging.getLogger(__name__) @@ -114,9 +115,14 @@ async def async_setup_entry( SERVICE_START_COMPUTER_TO_WINDOWS, ) platform.async_register_entity_service( - RESTART_COMPUTER, + SERVICE_RESTART_COMPUTER, {}, - RESTART_COMPUTER, + SERVICE_RESTART_COMPUTER, + ) + platform.async_register_entity_service( + SERVICE_CHANGE_MONITORS_CONFIG, + {}, + SERVICE_CHANGE_MONITORS_CONFIG, ) @@ -249,6 +255,10 @@ class ComputerSwitch(SwitchEntity): else: utils.restart_system(self._connection) + def change_monitors_config(self, yaml_config: str) -> None: + """Change the monitors configuration using a YAML config file.""" + utils.change_monitors_config(self._connection, yaml_config) + def update(self) -> None: """Ping the computer to see if it is online and update the state.""" ping_cmd = [ diff --git a/custom_components/easy_computer_manage/utils.py b/custom_components/easy_computer_manage/utils.py index 566cc22..2d716fa 100644 --- a/custom_components/easy_computer_manage/utils.py +++ b/custom_components/easy_computer_manage/utils.py @@ -1,6 +1,8 @@ import logging +import re import fabric2 +import yaml from fabric2 import Connection _LOGGER = logging.getLogger(__name__) @@ -202,3 +204,74 @@ def restart_to_windows_from_linux(connection: Connection): _LOGGER.error( "Could not restart system running on %s to Windows from Linux, system does not appear to be a Linux-based OS.", connection.host) + + +def change_monitors_config(connection: Connection, monitors_config: str): + """From a YAML config, changes the monitors configuration on the host, only works on Linux and Gnome (for now).""" + # TODO: Add support for Windows + + if is_unix_system(connection): + command_parts = ["gnome-monitor-config", "set"] + + # Convert str to dict (yaml) + monitors_config = yaml.safe_load(monitors_config) + + for monitor, settings in monitors_config.get('monitors', {}).items(): + if settings.get('enabled', False): + if 'primary' in settings and settings['primary']: + command_parts.append(f'-LpM {monitor}') + else: + command_parts.append(f'-LM {monitor}') + + if 'position' in settings: + command_parts.append(f'-x {settings["position"][0]} -y {settings["position"][1]}') + + if 'mode' in settings: + command_parts.append(f'-m {settings["mode"]}') + + if 'scale' in settings: + command_parts.append(f'-s {settings["scale"]}') + + if 'transform' in settings: + command_parts.append(f'-t {settings["transform"]}') + + command = ' '.join(command_parts) + + _LOGGER.debug("Running command: %s", command) + result = connection.run(command) + + if result.return_code == 0: + _LOGGER.info("Successfully changed monitors config on system running on %s.", connection.host) + else: + _LOGGER.error("Could not change monitors config on system running on %s", connection.host) + # TODO : add useful debug info + else: + _LOGGER.error("Not implemented yet.") + + +def parse_gnome_monitor_config(output): + # SHOULD NOT BE USED YET, STILL IN DEVELOPMENT + """Parse the output of the gnome-monitor-config command to get the current monitor configuration.""" + + monitors = [] + current_monitor = None + + for line in output.split('\n'): + monitor_match = re.match(r'^Monitor \[ (.+?) \] (ON|OFF)$', line) + if monitor_match: + if current_monitor: + monitors.append(current_monitor) + source, status = monitor_match.groups() + current_monitor = {'source': source, 'status': status, 'names': [], 'resolutions': []} + elif current_monitor: + display_name_match = re.match(r'^\s+display-name: (.+)$', line) + resolution_match = re.match(r'^\s+(\d+x\d+@\d+(?:\.\d+)?).*$', line) + if display_name_match: + current_monitor['names'].append(display_name_match.group(1).replace('"', '')) + elif resolution_match: + current_monitor['resolutions'].append(resolution_match.group(1)) + + if current_monitor: + monitors.append(current_monitor) + + return monitors