mirror of
https://git.srhtnin.garden/sarah/SaraSuite.git
synced 2026-10-14 02:28:11 +02:00
Initial commit
This commit is contained in:
commit
bea0f43947
25 files changed
+2327
No files matched your search
@@ -0,0 +1,182 @@
|
||||
"""Shared app-wide constants: paths, palette, theme.
|
||||
|
||||
The palette and theming mirror SaraFem's Core.py (which mirrors SaraView's),
|
||||
and read the same shared ~/.config/SaraSuite/Themes/*.toml files, so a theme
|
||||
picked for one Sara program looks the same in all of them.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import flet as ft
|
||||
import tomlkit
|
||||
|
||||
AppName = "SaraSuite"
|
||||
FontFamily = "Fixedsys Excelsior"
|
||||
|
||||
Home = Path.home()
|
||||
ConfigHome = Home / ".config"
|
||||
DataHome = Home / ".local" / "share"
|
||||
CacheHome = Home / ".cache"
|
||||
|
||||
SharedConfigDir = ConfigHome / "SaraSuite"
|
||||
ThemesDir = SharedConfigDir / "Themes"
|
||||
ConfigDir = SharedConfigDir / "SaraSuite"
|
||||
ConfigPath = ConfigDir / "Config.toml"
|
||||
|
||||
# Everything SaraSuite itself keeps about installed programs. Nothing in here
|
||||
# is user-edited, so it's all fair game to delete when a program is removed.
|
||||
DataDir = DataHome / "SaraSuite"
|
||||
ProgramsDir = DataDir / "Programs" # installed program files + venvs
|
||||
ReceiptsDir = DataDir / "Receipts" # what each install placed, for exact removal
|
||||
PristineDir = DataDir / "Pristine" # the default configs as last installed (merge base)
|
||||
BackupsDir = DataDir / "Backups" # displaced files and pre-merge config copies
|
||||
DownloadsDir = CacheHome / "SaraSuite" / "Downloads" # extracted source archives
|
||||
|
||||
# Bundled read-only files (program manifests, fonts). Inside a PyInstaller
|
||||
# build they're unpacked to sys._MEIPASS rather than next to the code.
|
||||
BundleDir = Path(getattr(sys, "_MEIPASS", Path(__file__).resolve().parent))
|
||||
ManifestsDir = BundleDir / "Programs"
|
||||
AssetsDir = BundleDir / "assets"
|
||||
|
||||
|
||||
class Palette:
|
||||
"""Live values for the active theme (see ApplyTheme). Same attribute names
|
||||
and SaraView role mapping as SaraFem's Palette."""
|
||||
|
||||
Mauve = "#cba6f7"
|
||||
Red = "#f38ba8"
|
||||
Peach = "#fab387"
|
||||
Yellow = "#f9e2af"
|
||||
Green = "#a6e3a1"
|
||||
Blue = "#89b4fa"
|
||||
Text = "#cdd6f4"
|
||||
White = "#cdd6f4"
|
||||
Overlay0 = "#6c7086"
|
||||
Surface1 = "#45475a"
|
||||
Surface0 = "#313244"
|
||||
Base = "#1e1e2e"
|
||||
Mantle = "#181825"
|
||||
Crust = "#11111b"
|
||||
|
||||
|
||||
_ThemeRoleMap = {
|
||||
"Background": "Base", "Surface": "Mantle", "SurfaceAlt": "Surface0", "Border": "Surface1",
|
||||
"Text": "Text", "TextMuted": "Overlay0", "Black": "Crust", "White": "White",
|
||||
"Red": "Red", "Orange": "Peach", "Yellow": "Yellow", "Green": "Green",
|
||||
"Blue": "Blue", "Purple": "Mauve",
|
||||
}
|
||||
|
||||
|
||||
def ApplyTheme(ThemeName: str) -> None:
|
||||
"""Mutate Palette in place from ~/.config/SaraSuite/Themes/<ThemeName>.toml.
|
||||
Falls back to the built-in Catppuccin Mocha values if the file is missing
|
||||
(SaraFem seeds that directory; SaraSuite only reads it). Must run before
|
||||
any control reads Palette.X."""
|
||||
ThemeFile = ThemesDir / f"{ThemeName}.toml"
|
||||
if not ThemeFile.exists():
|
||||
return
|
||||
try:
|
||||
RoleValues = tomlkit.parse(ThemeFile.read_text()).unwrap().get("Palette", {})
|
||||
except tomlkit.exceptions.TOMLKitError:
|
||||
return
|
||||
for RoleName, AttrName in _ThemeRoleMap.items():
|
||||
if RoleName in RoleValues:
|
||||
setattr(Palette, AttrName, RoleValues[RoleName])
|
||||
|
||||
|
||||
@dataclass
|
||||
class AppConfig:
|
||||
Theme: str = "CatppuccinMocha" # matches a file in ~/.config/SaraSuite/Themes/
|
||||
# Where the Sara repos live (Gitea on SpineS, Tailscale-only); manifests
|
||||
# use it as {GitHost}.
|
||||
GitHost: str = "http://git.srhtnin.garden/sarah"
|
||||
|
||||
def Save(self) -> None:
|
||||
ConfigDir.mkdir(parents=True, exist_ok=True)
|
||||
ConfigPath.write_text(tomlkit.dumps(asdict(self)))
|
||||
|
||||
|
||||
def LoadConfig() -> AppConfig:
|
||||
if not ConfigPath.exists():
|
||||
Config = AppConfig()
|
||||
Config.Save()
|
||||
return Config
|
||||
try:
|
||||
Data = tomlkit.parse(ConfigPath.read_text()).unwrap()
|
||||
except tomlkit.exceptions.TOMLKitError:
|
||||
return AppConfig()
|
||||
KnownFields = set(AppConfig.__dataclass_fields__)
|
||||
return AppConfig(**{Key: Value for Key, Value in Data.items() if Key in KnownFields})
|
||||
|
||||
|
||||
def ChildEnvironment() -> dict[str, str]:
|
||||
"""The environment for child processes (systemctl, pip, python3 -m venv).
|
||||
A PyInstaller build points LD_LIBRARY_PATH at its own bundled libraries
|
||||
and keeps the original in LD_LIBRARY_PATH_ORIG; system tools need the
|
||||
original back or they can load the wrong libraries."""
|
||||
Environment = dict(os.environ)
|
||||
if getattr(sys, "frozen", False):
|
||||
Original = Environment.pop("LD_LIBRARY_PATH_ORIG", None)
|
||||
if Original is not None:
|
||||
Environment["LD_LIBRARY_PATH"] = Original
|
||||
else:
|
||||
Environment.pop("LD_LIBRARY_PATH", None)
|
||||
for Key in ("PYTHONHOME", "PYTHONPATH"):
|
||||
Environment.pop(Key, None)
|
||||
return Environment
|
||||
|
||||
|
||||
def CurrentDesktop() -> str:
|
||||
"""First entry of $XDG_CURRENT_DESKTOP as-is (e.g. "COSMIC"), used for
|
||||
portal descriptors' UseIn= and the <desktop>-portals.conf file name."""
|
||||
return os.environ.get("XDG_CURRENT_DESKTOP", "").split(":")[0]
|
||||
|
||||
|
||||
def BuildTheme() -> ft.Theme:
|
||||
ColorScheme = ft.ColorScheme(
|
||||
primary=Palette.Mauve,
|
||||
on_primary=Palette.Crust,
|
||||
primary_container=Palette.Surface0,
|
||||
on_primary_container=Palette.Mauve,
|
||||
secondary=Palette.Mauve,
|
||||
on_secondary=Palette.Crust,
|
||||
secondary_container=Palette.Surface0,
|
||||
on_secondary_container=Palette.Mauve,
|
||||
surface=Palette.Base,
|
||||
on_surface=Palette.Text,
|
||||
on_surface_variant=Palette.Overlay0,
|
||||
surface_container=Palette.Mantle,
|
||||
surface_container_low=Palette.Mantle,
|
||||
surface_container_high=Palette.Surface0,
|
||||
surface_container_highest=Palette.Surface1,
|
||||
outline=Palette.Surface1,
|
||||
outline_variant=Palette.Surface1,
|
||||
error=Palette.Red,
|
||||
on_error=Palette.Crust,
|
||||
)
|
||||
return ft.Theme(
|
||||
font_family=FontFamily,
|
||||
color_scheme=ColorScheme,
|
||||
use_material3=True,
|
||||
scaffold_bgcolor=Palette.Base,
|
||||
canvas_color=Palette.Base,
|
||||
dialog_theme=ft.DialogTheme(
|
||||
bgcolor=Palette.Mantle,
|
||||
shape=ft.RoundedRectangleBorder(radius=8, side=ft.BorderSide(1, Palette.Mauve)),
|
||||
title_text_style=ft.TextStyle(color=Palette.Mauve, size=18, font_family=FontFamily),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def ChipButtonStyle(Accent: str = "") -> ft.ButtonStyle:
|
||||
"""SaraView's button look: SurfaceAlt fill, 1px accent border, small radius.
|
||||
Accent defaults to Mauve; Remove buttons pass Palette.Red."""
|
||||
return ft.ButtonStyle(
|
||||
color=Palette.Text,
|
||||
bgcolor=Palette.Surface0,
|
||||
side=ft.BorderSide(1, Accent or Palette.Mauve),
|
||||
shape=ft.RoundedRectangleBorder(radius=4),
|
||||
)
|
||||
Reference in new issue
Block a user