pragma Singleton // Installed fonts, split by what they can actually be used for. // // Two different questions with different answers: which families are readable // as interface text, and which carry the Nerd Font glyphs the shell draws its // icons with. Offering one list for both is how you end up with a desktop full // of tofu, so they stay separate. // // Enumerated once on demand. Fonts do not appear while you are looking at a // settings page, and fc-list over a few hundred families is not free. import Quickshell import Quickshell.Io import QtQuick import qs.config Singleton { id: root readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-fonts" property var interfaceFonts: [] property var monospaceFonts: [] property var iconFonts: [] property bool scanning: false property string lastError: "" readonly property string interfaceFont: DesktopPreferences.get("interfaceFont") readonly property string iconFont: DesktopPreferences.get("iconFont") readonly property bool loaded: root.interfaceFonts.length > 0 // True when the stored family is not installed. fontconfig silently // substitutes something else, so the shell keeps working and quietly stops // looking the way it was configured to -- worth saying out loud. readonly property bool interfaceMissing: root.loaded && root.interfaceFonts.indexOf(root.interfaceFont) < 0 readonly property bool iconMissing: root.iconFonts.length > 0 && root.iconFonts.indexOf(root.iconFont) < 0 Process { id: scan command: [root.helperPath] stdout: StdioCollector { onStreamFinished: { try { const parsed = JSON.parse(this.text); root.interfaceFonts = parsed.interface ?? []; root.monospaceFonts = parsed.monospace ?? []; root.iconFonts = parsed.icons ?? []; root.lastError = ""; } catch (error) { root.lastError = "The installed fonts could not be read."; } root.scanning = false; } } onExited: (exitCode, exitStatus) => { root.scanning = false; if (exitCode !== 0) root.lastError = "The installed fonts could not be read."; } } Component.onCompleted: root.refresh() function refresh(): void { if (scan.running) return; root.scanning = true; scan.running = true; } // Only a family this machine reported is accepted, so a hand-edited // settings file cannot put arbitrary text where a font name belongs -- it // reaches hl.config as a string on the compositor side. function setInterface(family: string): bool { if (root.interfaceFonts.indexOf(family) < 0) { root.lastError = "That font is not installed."; return false; } return root.store("interfaceFont", family); } function setIcon(family: string): bool { if (root.iconFonts.indexOf(family) < 0) { root.lastError = "That font does not carry icon glyphs."; return false; } return root.store("iconFont", family); } function store(key: string, family: string): bool { if (!DesktopPreferences.set(key, family)) { root.lastError = "That font name could not be saved."; return false; } root.lastError = ""; return true; } }