From 8d11f60201643e1da0f90796505e1d5bf2d2778f Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Fri, 20 Feb 2026 15:21:14 +0000 Subject: [PATCH] move settings and configscreen into their own files rename tcss file --- src/q3rcon_tui/configscreen.py | 64 ++++++++++++++++++ .../{rcon_tui.tcss => q3rcon_tui.tcss} | 0 src/q3rcon_tui/settings.py | 67 +++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 src/q3rcon_tui/configscreen.py rename src/q3rcon_tui/{rcon_tui.tcss => q3rcon_tui.tcss} (100%) create mode 100644 src/q3rcon_tui/settings.py diff --git a/src/q3rcon_tui/configscreen.py b/src/q3rcon_tui/configscreen.py new file mode 100644 index 0000000..edbdd66 --- /dev/null +++ b/src/q3rcon_tui/configscreen.py @@ -0,0 +1,64 @@ +from textual.app import ComposeResult +from textual.containers import Horizontal, Vertical +from textual.screen import ModalScreen +from textual.widgets import Button, Input, Label, Static + +from .settings import settings + + +class ConfigScreen(ModalScreen[bool]): + """Modal dialog for configuring connection settings.""" + + def __init__(self, current_host: str, current_port: int, current_password: str): + super().__init__() + self.current_host = current_host + self.current_port = current_port + self.current_password = current_password + + def compose(self) -> ComposeResult: + with Vertical(id='config-dialog'): + yield Static('Connection Configuration', id='config-title') + with Vertical(id='config-form'): + yield Label('Host:') + yield Input( + value=self.current_host, placeholder='localhost', id='host-input' + ) + yield Label('Port:') + yield Input( + value=str(self.current_port), placeholder='28960', id='port-input' + ) + yield Label('Password:') + yield Input( + value=self.current_password, + placeholder='Enter password', + password=True, + id='password-input', + ) + with Horizontal(id='config-buttons'): + yield Button('Save', variant='success', id='config-save') + yield Button('Cancel', variant='error', id='config-cancel') + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == 'config-save': + try: + new_host = ( + self.query_one('#host-input', Input).value.strip() or 'localhost' + ) + new_port = int(self.query_one('#port-input', Input).value or '28960') + new_password = self.query_one('#password-input', Input).value + + if new_port < 1 or new_port > 65535: + raise ValueError('Port must be between 1 and 65535') + + if len(new_password) < 8: + raise ValueError('Password must be at least 8 characters long') + + settings.host = new_host + settings.port = new_port + settings.password = new_password + + self.dismiss(True) + except ValueError: + self.app.bell() + elif event.button.id == 'config-cancel': + self.dismiss(False) diff --git a/src/q3rcon_tui/rcon_tui.tcss b/src/q3rcon_tui/q3rcon_tui.tcss similarity index 100% rename from src/q3rcon_tui/rcon_tui.tcss rename to src/q3rcon_tui/q3rcon_tui.tcss diff --git a/src/q3rcon_tui/settings.py b/src/q3rcon_tui/settings.py new file mode 100644 index 0000000..a341dd4 --- /dev/null +++ b/src/q3rcon_tui/settings.py @@ -0,0 +1,67 @@ +from pathlib import Path +from typing import Annotated, Type + +from loguru import logger +from pydantic import AfterValidator, BeforeValidator +from pydantic_settings import BaseSettings, CliSettingsSource, SettingsConfigDict + +from .__about__ import __version__ as version + + +def version_callback(value: bool) -> bool | None: + if value: + print(f'q3rcon-tui version: {version}') + raise SystemExit(0) + return False + + +def is_valid_password(password: str) -> str | None: + if len(password) < 8: + raise ValueError('Password must be at least 8 characters long') + return password + + +class Settings(BaseSettings): + host: str = 'localhost' + port: int = 28960 + password: Annotated[str, AfterValidator(is_valid_password)] = '' + append: bool = False + raw: bool = False + version: Annotated[bool, BeforeValidator(version_callback)] = False + + model_config = SettingsConfigDict( + env_file=( + '.env', + Path.home() / '.config' / 'q3rcon-tui' / 'config.env', + ), + env_file_encoding='utf-8', + env_prefix='Q3RCON_TUI_', + cli_prefix='', + cli_parse_args=True, + cli_implicit_flags=True, + validate_assignment=False, + frozen=False, + ) + + @classmethod + def settings_customise_sources( + cls, + settings_cls: Type[BaseSettings], + init_settings: ..., + env_settings: ..., + dotenv_settings: ..., + file_secret_settings: ..., + ) -> tuple: + return ( + CliSettingsSource(settings_cls), + env_settings, + dotenv_settings, + init_settings, + ) + + +try: + settings = Settings() +except ValueError as e: + logger.error(e) + raise SystemExit(1)