"""Foreground process sensor and binary-sensor entities.""" from __future__ import annotations import logging from datetime import datetime, timezone from typing import Any from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN from .foreground_coordinator import ForegroundCoordinator _LOGGER = logging.getLogger(__name__) def _foreground_device_info(entry: ConfigEntry) -> DeviceInfo: """All foreground entities share one HA device, linked to the hub.""" return DeviceInfo( identifiers={(DOMAIN, f"{entry.entry_id}_foreground")}, via_device=(DOMAIN, entry.entry_id), name="Foreground", manufacturer="Remote Media Player", model="Foreground Process", ) class _ForegroundEntityBase(CoordinatorEntity[ForegroundCoordinator]): """Boilerplate shared by every foreground entity.""" _attr_has_entity_name = True def __init__( self, coordinator: ForegroundCoordinator, entry: ConfigEntry, ) -> None: super().__init__(coordinator) self._entry = entry self._attr_device_info = _foreground_device_info(entry) @property def _data(self) -> dict[str, Any]: return self.coordinator.data or {} @property def available(self) -> bool: # Coordinator availability covers HTTP failures; the per-platform # ``available`` flag in the payload reports e.g. "Wayland session". if not super().available: return False return bool(self._data.get("available", True)) class ForegroundProcessSensor(_ForegroundEntityBase, SensorEntity): """Primary sensor: the process name plus full payload as attributes.""" _attr_icon = "mdi:application" _attr_translation_key = "foreground_process" def __init__( self, coordinator: ForegroundCoordinator, entry: ConfigEntry, ) -> None: super().__init__(coordinator, entry) self._attr_unique_id = f"{entry.entry_id}_foreground_process" @property def native_value(self) -> str | None: return self._data.get("process_name") @property def extra_state_attributes(self) -> dict[str, Any]: d = self._data return { "pid": d.get("pid"), "executable_path": d.get("executable_path"), "window_title": d.get("window_title"), "window_handle": d.get("window_handle"), "is_fullscreen": d.get("is_fullscreen"), "is_minimized": d.get("is_minimized"), "monitor_id": d.get("monitor_id"), "monitor_geometry": d.get("monitor_geometry"), "window_geometry": d.get("window_geometry"), "started_at": d.get("started_at"), "platform": d.get("platform"), "is_browser": d.get("is_browser"), "browser_page_title": d.get("browser_page_title"), "browser_url": d.get("browser_url"), "available": d.get("available"), "error": d.get("error"), } class ForegroundWindowTitleSensor(_ForegroundEntityBase, SensorEntity): _attr_icon = "mdi:window-restore" _attr_translation_key = "window_title" def __init__( self, coordinator: ForegroundCoordinator, entry: ConfigEntry, ) -> None: super().__init__(coordinator, entry) self._attr_unique_id = f"{entry.entry_id}_foreground_window_title" @property def native_value(self) -> str | None: return self._data.get("window_title") class ForegroundPidSensor(_ForegroundEntityBase, SensorEntity): _attr_icon = "mdi:identifier" _attr_translation_key = "pid" _attr_entity_registry_enabled_default = False # diagnostic-leaning def __init__( self, coordinator: ForegroundCoordinator, entry: ConfigEntry, ) -> None: super().__init__(coordinator, entry) self._attr_unique_id = f"{entry.entry_id}_foreground_pid" @property def native_value(self) -> int | None: return self._data.get("pid") class ForegroundMonitorSensor(_ForegroundEntityBase, SensorEntity): _attr_icon = "mdi:monitor" _attr_translation_key = "foreground_monitor" def __init__( self, coordinator: ForegroundCoordinator, entry: ConfigEntry, ) -> None: super().__init__(coordinator, entry) self._attr_unique_id = f"{entry.entry_id}_foreground_monitor" @property def native_value(self) -> int | None: return self._data.get("monitor_id") class ForegroundStartedAtSensor(_ForegroundEntityBase, SensorEntity): """Process start time as a timezone-aware datetime.""" _attr_icon = "mdi:clock-start" _attr_translation_key = "process_started" _attr_device_class = SensorDeviceClass.TIMESTAMP _attr_entity_registry_enabled_default = False def __init__( self, coordinator: ForegroundCoordinator, entry: ConfigEntry, ) -> None: super().__init__(coordinator, entry) self._attr_unique_id = f"{entry.entry_id}_foreground_started_at" @property def native_value(self) -> datetime | None: ts = self._data.get("started_at") if ts is None: return None try: return datetime.fromtimestamp(float(ts), tz=timezone.utc) except (TypeError, ValueError, OSError): return None class ForegroundFullscreenBinarySensor(_ForegroundEntityBase, BinarySensorEntity): _attr_icon = "mdi:fullscreen" _attr_translation_key = "fullscreen" def __init__( self, coordinator: ForegroundCoordinator, entry: ConfigEntry, ) -> None: super().__init__(coordinator, entry) self._attr_unique_id = f"{entry.entry_id}_foreground_fullscreen" @property def is_on(self) -> bool: return bool(self._data.get("is_fullscreen")) class ForegroundMinimizedBinarySensor(_ForegroundEntityBase, BinarySensorEntity): _attr_icon = "mdi:window-minimize" _attr_translation_key = "minimized" _attr_entity_registry_enabled_default = False def __init__( self, coordinator: ForegroundCoordinator, entry: ConfigEntry, ) -> None: super().__init__(coordinator, entry) self._attr_unique_id = f"{entry.entry_id}_foreground_minimized" @property def is_on(self) -> bool: return bool(self._data.get("is_minimized")) FOREGROUND_SENSORS: tuple[type[_ForegroundEntityBase], ...] = ( ForegroundProcessSensor, ForegroundWindowTitleSensor, ForegroundPidSensor, ForegroundMonitorSensor, ForegroundStartedAtSensor, ) FOREGROUND_BINARY_SENSORS: tuple[type[_ForegroundEntityBase], ...] = ( ForegroundFullscreenBinarySensor, ForegroundMinimizedBinarySensor, )