150 lines
4.0 KiB
Python
150 lines
4.0 KiB
Python
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
from nirimod.kdl_parser import (
|
|
NIRI_CONFIG,
|
|
KdlNode,
|
|
load_niri_config_multi,
|
|
parse_kdl,
|
|
save_niri_config,
|
|
save_niri_config_multi,
|
|
write_kdl,
|
|
)
|
|
from nirimod.undo import UndoEntry, UndoManager
|
|
|
|
if TYPE_CHECKING:
|
|
pass
|
|
|
|
|
|
@dataclass
|
|
class RuntimeInfo:
|
|
|
|
niri_running: bool = False
|
|
has_touchpad: bool = False
|
|
|
|
|
|
class AppState:
|
|
|
|
def __init__(self) -> None:
|
|
self._nodes: list[KdlNode] = []
|
|
self._saved_kdl: str = ""
|
|
self._undo: UndoManager = UndoManager()
|
|
self._runtime: RuntimeInfo = RuntimeInfo()
|
|
self._dirty: bool = False
|
|
self._include_slots: list[tuple[KdlNode, Path]] = []
|
|
self._source_files: set[Path] = set()
|
|
|
|
def load(self) -> None:
|
|
from nirimod import niri_ipc
|
|
|
|
self._runtime = RuntimeInfo(
|
|
niri_running=niri_ipc.is_niri_running(),
|
|
has_touchpad=niri_ipc.has_touchpad(),
|
|
)
|
|
self._nodes, self._include_slots = load_niri_config_multi()
|
|
self._source_files = {NIRI_CONFIG}
|
|
for _, path in self._include_slots:
|
|
if path.exists():
|
|
self._source_files.add(path)
|
|
self._saved_kdl = write_kdl(self._nodes) if self._nodes else ""
|
|
self._dirty = False
|
|
|
|
@property
|
|
def nodes(self) -> list[KdlNode]:
|
|
return self._nodes
|
|
|
|
@nodes.setter
|
|
def nodes(self, value: list[KdlNode]) -> None:
|
|
self._nodes = value
|
|
|
|
@property
|
|
def saved_kdl(self) -> str:
|
|
return self._saved_kdl
|
|
|
|
@property
|
|
def source_files(self) -> set[Path]:
|
|
return self._source_files
|
|
|
|
@property
|
|
def include_slots(self) -> list[tuple[KdlNode, Path]]:
|
|
return self._include_slots
|
|
|
|
@property
|
|
def is_multi_file(self) -> bool:
|
|
return bool(self._include_slots)
|
|
|
|
@property
|
|
def niri_running(self) -> bool:
|
|
return self._runtime.niri_running
|
|
|
|
@property
|
|
def has_touchpad(self) -> bool:
|
|
return self._runtime.has_touchpad
|
|
|
|
@property
|
|
def is_dirty(self) -> bool:
|
|
return self._dirty
|
|
|
|
def mark_dirty(self) -> None:
|
|
self._dirty = True
|
|
|
|
def mark_clean(self) -> None:
|
|
self._dirty = False
|
|
|
|
@property
|
|
def undo(self) -> UndoManager:
|
|
return self._undo
|
|
|
|
def push_undo(self, description: str, before: str, after: str) -> None:
|
|
self._undo.push(UndoEntry(description, before, after))
|
|
|
|
def apply_undo(self) -> UndoEntry | None:
|
|
entry = self._undo.pop_undo()
|
|
if entry is None:
|
|
return None
|
|
self._nodes = parse_kdl(entry.snapshot_before)
|
|
self._dirty = entry.snapshot_before != self._saved_kdl
|
|
return entry
|
|
|
|
def apply_redo(self) -> UndoEntry | None:
|
|
entry = self._undo.pop_redo()
|
|
if entry is None:
|
|
return None
|
|
self._nodes = parse_kdl(entry.snapshot_after)
|
|
self._dirty = entry.snapshot_after != self._saved_kdl
|
|
return entry
|
|
|
|
def discard(self) -> None:
|
|
self._nodes = parse_kdl(self._saved_kdl) if self._saved_kdl else []
|
|
self._undo.clear()
|
|
self._dirty = False
|
|
|
|
def commit_save(self, new_kdl: str) -> None:
|
|
self._saved_kdl = new_kdl
|
|
self._undo.clear()
|
|
self._dirty = False
|
|
|
|
def reload_from_disk(self) -> None:
|
|
self._nodes, self._include_slots = load_niri_config_multi()
|
|
self._source_files = {NIRI_CONFIG}
|
|
for _, path in self._include_slots:
|
|
if path.exists():
|
|
self._source_files.add(path)
|
|
|
|
def write_current_kdl(self) -> str:
|
|
return write_kdl(self._nodes)
|
|
|
|
def write_to_path(self, path: Path | None = None) -> None:
|
|
if path is not None:
|
|
# Explicit path (e.g. validation temp file) — single file write
|
|
save_niri_config(self._nodes, path=path)
|
|
elif self._include_slots:
|
|
save_niri_config_multi(self._nodes, self._include_slots)
|
|
else:
|
|
save_niri_config(self._nodes)
|