Security: - Fix path traversal in _save/_load_last_session by rejecting usernames starting with dot (blocks '..' and hidden file creation) - Add avatar file size limit (10 MB) to prevent DoS via large ~/.face - Add session_name length validation on write (symmetric with read) - Add payload size check to send_message (symmetric with recv_message) - Set log level to INFO in production (was DEBUG) Quality: - Eliminate main-thread blocking on user switch: _cancel_pending_session now sets a cancellation event and closes the socket instead of doing blocking IPC. The login worker checks the event after each step. - Move power actions (reboot/shutdown) to background threads - Catch TimeoutExpired in addition to CalledProcessError for power actions - Consolidate socket cleanup in _login_worker via finally block, remove redundant _close_greetd_sock calls from error callbacks - Fix _select_initial_user to return False for GLib.idle_add deregistration - Fix context manager leak in resolve_wallpaper_path on exception - Pass Config object to GreeterWindow instead of loading it twice
85 lines
2.3 KiB
Python
85 lines
2.3 KiB
Python
# ABOUTME: Configuration loading from moongreet.toml.
|
|
# ABOUTME: Parses appearance and behavior settings with wallpaper path resolution.
|
|
|
|
import tomllib
|
|
from contextlib import AbstractContextManager
|
|
from dataclasses import dataclass
|
|
from importlib.resources import as_file, files
|
|
from pathlib import Path
|
|
|
|
DEFAULT_CONFIG_PATHS = [
|
|
Path("/etc/moongreet/moongreet.toml"),
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class Config:
|
|
"""Greeter configuration loaded from moongreet.toml."""
|
|
|
|
background: Path | None = None
|
|
gtk_theme: str | None = None
|
|
|
|
|
|
def load_config(config_path: Path | None = None) -> Config:
|
|
"""Load configuration from a TOML file.
|
|
|
|
Relative paths in the config are resolved against the config file's directory.
|
|
"""
|
|
if config_path is None:
|
|
for path in DEFAULT_CONFIG_PATHS:
|
|
if path.exists():
|
|
config_path = path
|
|
break
|
|
if config_path is None:
|
|
return Config()
|
|
|
|
if not config_path.exists():
|
|
return Config()
|
|
|
|
try:
|
|
with open(config_path, "rb") as f:
|
|
data = tomllib.load(f)
|
|
except (tomllib.TOMLDecodeError, OSError):
|
|
return Config()
|
|
|
|
config = Config()
|
|
appearance = data.get("appearance", {})
|
|
|
|
bg = appearance.get("background")
|
|
if bg:
|
|
bg_path = Path(bg)
|
|
if not bg_path.is_absolute():
|
|
bg_path = config_path.parent / bg_path
|
|
config.background = bg_path
|
|
|
|
gtk_theme = appearance.get("gtk-theme")
|
|
if gtk_theme:
|
|
config.gtk_theme = gtk_theme
|
|
|
|
return config
|
|
|
|
|
|
_PACKAGE_DATA = files("moongreet") / "data"
|
|
_DEFAULT_WALLPAPER_PATH = _PACKAGE_DATA / "wallpaper.jpg"
|
|
|
|
|
|
def resolve_wallpaper_path(
|
|
config: Config,
|
|
) -> tuple[Path, AbstractContextManager | None]:
|
|
"""Resolve the wallpaper path from config or fall back to the package default.
|
|
|
|
Returns (path, context_manager). The context_manager is non-None when a
|
|
package resource was extracted to a temporary file — the caller must keep
|
|
it alive and call __exit__ when done.
|
|
"""
|
|
if config.background and config.background.exists():
|
|
return config.background, None
|
|
|
|
ctx = as_file(_DEFAULT_WALLPAPER_PATH)
|
|
try:
|
|
path = ctx.__enter__()
|
|
except Exception:
|
|
ctx.__exit__(None, None, None)
|
|
raise
|
|
return path, ctx
|