"""All-apps data source (AstalApps, same library astal-menu's appdrawer uses) plus favorite-app persistence — a plain JSON list of desktop-entry IDs under ~/.local/state/horizon-dock/favorites.json (see paths.py for why STATE_DIR, not CONFIG_DIR).""" from __future__ import annotations import json import gi gi.require_version("AstalApps", "0.1") from gi.repository import AstalApps # noqa: E402 from paths import FAVORITES_FILE, ensure_dirs class AppSource: def __init__(self) -> None: self._apps = AstalApps.Apps() self._favorites: list[str] = self._load_favorites() self._wm_index: dict[str, str] = {} # -- listing ------------------------------------------------------------- def all_apps(self) -> list: """Every installed app, alphabetical by name.""" apps = list(self._apps.get_list()) apps.sort(key=lambda a: (a.get_name() or "").lower()) return apps def icon_for_window(self, window: dict) -> str: """Map an open window's wm class to its installed app's icon — same wm-class/executable/name index astal-menu/ui/taskbar.py builds for its own taskbar, rebuilt lazily here on first use.""" if not self._wm_index: for app in self._apps.get_list(): for key in (app.get_wm_class(), app.get_executable(), app.get_name()): if key: self._wm_index.setdefault(key.lower(), app.get_icon_name()) cls = (window.get("class") or "").lower() return self._wm_index.get(cls) or cls or "application-x-executable" def favorite_apps(self) -> list: """Favorited apps, in the order they were pinned. Entries whose app has since been uninstalled are silently dropped (not un-favorited — the desktop file may just be temporarily missing, e.g. mid-update).""" by_entry = {a.get_entry(): a for a in self._apps.get_list()} return [by_entry[e] for e in self._favorites if e in by_entry] @staticmethod def launch(app) -> None: app.launch() # -- favorites persistence ------------------------------------------------ def is_favorite(self, app) -> bool: return app.get_entry() in self._favorites def toggle_favorite(self, app) -> None: entry = app.get_entry() if not entry: return if entry in self._favorites: self._favorites.remove(entry) else: self._favorites.append(entry) self._save_favorites() def _load_favorites(self) -> list[str]: try: data = json.loads(FAVORITES_FILE.read_text()) return [e for e in data if isinstance(e, str)] except (FileNotFoundError, json.JSONDecodeError): return [] def _save_favorites(self) -> None: ensure_dirs() FAVORITES_FILE.write_text(json.dumps(self._favorites, indent=2) + "\n")