#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Resolution Service Module Centralized service for managing resolution settings across CLI and GUI frontends """ import logging from typing import Optional from ..handlers.config_handler import ConfigHandler # Initialize logger logger = logging.getLogger(__name__) class ResolutionService: """ Centralized service for managing resolution settings Handles saving, loading, and validation of resolution settings """ COMMON_RESOLUTIONS = [ "1280x720", "1280x800 (Steam Deck)", "1366x768", "1440x900", "1600x900", "1600x1200", "1680x1050", "1920x1080", "1920x1200", "2048x1152", "2560x1080", "2560x1440", "2560x1600", "3440x1440", "3840x1080", "3840x1600", "3840x2160", "3840x2400", "5120x1440", "5120x2160", "7680x4320", ] def __init__(self): """Initialize the resolution service""" self.config_handler = ConfigHandler() logger.debug("ResolutionService initialized") def save_resolution(self, resolution: str) -> bool: """ Save a resolution setting to configuration Args: resolution (str): The resolution to save (e.g., '1920x1080') Returns: bool: True if saved successfully, False otherwise """ try: # Validate resolution format (basic check) if not self._validate_resolution_format(resolution): logger.warning("Invalid resolution format provided") return False success = self.config_handler.save_resolution(resolution) if success: logger.info(f"Resolution saved successfully: {resolution}") else: logger.error("Failed to save resolution") return success except Exception as e: logger.error(f"Error in save_resolution: {e}") return False def get_saved_resolution(self) -> Optional[str]: """ Retrieve the saved resolution from configuration Returns: str: The saved resolution or None if not saved """ try: resolution = self.config_handler.get_saved_resolution() if resolution: logger.debug(f"Retrieved saved resolution: {resolution}") else: logger.debug("No saved resolution found") return resolution except Exception as e: logger.error(f"Error retrieving resolution: {e}") return None def has_saved_resolution(self) -> bool: """ Check if a resolution is saved in configuration Returns: bool: True if resolution exists, False otherwise """ try: return self.config_handler.has_saved_resolution() except Exception as e: logger.error(f"Error checking for saved resolution: {e}") return False def clear_saved_resolution(self) -> bool: """ Clear the saved resolution from configuration Returns: bool: True if cleared successfully, False otherwise """ try: success = self.config_handler.clear_saved_resolution() if success: logger.info("Resolution cleared successfully") else: logger.error("Failed to clear resolution") return success except Exception as e: logger.error(f"Error clearing resolution: {e}") return False def _validate_resolution_format(self, resolution: str) -> bool: """ Validate resolution format (e.g., '1920x1080' or '1280x800 (Steam Deck)') Args: resolution (str): Resolution string to validate Returns: bool: True if valid format, False otherwise """ import re if not resolution or resolution == 'Leave unchanged': return True # Allow 'Leave unchanged' as valid # Handle Steam Deck format: '1280x800 (Steam Deck)' if ' (Steam Deck)' in resolution: resolution = resolution.replace(' (Steam Deck)', '') # Check for WxH format (e.g., 1920x1080) if re.match(r'^[0-9]+x[0-9]+$', resolution): # Extract width and height try: width, height = resolution.split('x') width_int = int(width) height_int = int(height) # Basic sanity checks if width_int > 0 and height_int > 0 and width_int <= 10000 and height_int <= 10000: return True else: logger.warning(f"Resolution dimensions out of reasonable range: {resolution}") return False except ValueError: logger.warning(f"Invalid resolution format: {resolution}") return False else: logger.warning(f"Resolution does not match WxH format: {resolution}") return False def get_resolution_list(self) -> list: """ Build the resolution dropdown list: the curated common resolutions, plus any resolution reported by a connected Qt screen that isn't already covered, so unusual native resolutions (e.g. ultrawide) still show up. Falls back to the curated list alone if screen detection is unavailable (headless, sandboxed launch, etc). Returns: list: Resolution labels sorted by pixel dimensions """ resolutions = {} for label in self.COMMON_RESOLUTIONS: key = label.split(' ')[0] resolutions[key] = label try: from PySide6.QtGui import QGuiApplication for screen in QGuiApplication.screens(): size = screen.size() key = f"{size.width()}x{size.height()}" resolutions.setdefault(key, key) except Exception as e: logger.debug(f"Could not query Qt screens for resolution detection: {e}") def sort_key(label: str): width, height = label.split(' ')[0].split('x') return (int(width), int(height)) return sorted(resolutions.values(), key=sort_key) def get_resolution_index(self, resolution: str, combo_items: list) -> int: """ Get the index of a resolution in a combo box list Args: resolution (str): Resolution to find combo_items (list): List of combo box items Returns: int: Index of the resolution, or 0 (Leave unchanged) if not found """ if not resolution: return 0 # Default to 'Leave unchanged' # Handle Steam Deck special case if resolution == '1280x800' and '1280x800 (Steam Deck)' in combo_items: return combo_items.index('1280x800 (Steam Deck)') # Try exact match first if resolution in combo_items: return combo_items.index(resolution) # Try partial match (e.g., '1920x1080' in '1920x1080 (Steam Deck)') for i, item in enumerate(combo_items): if resolution in item: return i # Default to 'Leave unchanged' return 0