From cf55e2613314e27243c60395e58665b9309280c8 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Sat, 22 Feb 2025 19:58:07 +1100 Subject: config: use config file Config file at ~/.config/caelestia/shell.json --- app.tsx | 5 +- config.ts | 264 ++++++++++++++++++++++++------------------- src/modules/bar.tsx | 152 +++++++++++++++---------- src/modules/launcher.tsx | 23 ++-- src/modules/notifpopups.tsx | 2 +- src/modules/osds.tsx | 18 +-- src/utils/system.ts | 16 ++- src/widgets/notification.tsx | 5 +- src/widgets/popupwindow.ts | 2 +- 9 files changed, 282 insertions(+), 205 deletions(-) diff --git a/app.tsx b/app.tsx index 17bbeea..dd473d8 100644 --- a/app.tsx +++ b/app.tsx @@ -9,8 +9,9 @@ import Players from "@/services/players"; import type PopupWindow from "@/widgets/popupwindow"; import { execAsync, GLib, monitorFile, readFileAsync, writeFileAsync } from "astal"; import { App } from "astal/gtk3"; +import { initConfig, updateConfig } from "config"; -const loadStyleAsync = async () => { +export const loadStyleAsync = async () => { let schemeColours; if (GLib.file_test(`${STATE}/scheme/current.txt`, GLib.FileTest.EXISTS)) { const currentScheme = await readFileAsync(`${STATE}/scheme/current.txt`); @@ -33,6 +34,7 @@ App.start({ const now = Date.now(); loadStyleAsync().catch(console.error); monitorFile(`${STATE}/scheme/current.txt`, () => loadStyleAsync().catch(console.error)); + initConfig(); ; ; @@ -46,6 +48,7 @@ App.start({ requestHandler(request, res) { if (request === "quit") App.quit(); else if (request === "reload-css") loadStyleAsync().catch(console.error); + else if (request === "reload-config") updateConfig(); else if (request.startsWith("show")) App.get_window(request.split(" ")[1])?.show(); else if (request === "toggle sideleft") { const window = App.get_window("sideleft") as PopupWindow | null; diff --git a/config.ts b/config.ts index cb9a56b..8d9c2a8 100644 --- a/config.ts +++ b/config.ts @@ -1,140 +1,166 @@ +import { GLib, monitorFile, readFileAsync, Variable } from "astal"; import { Astal } from "astal/gtk3"; +import { loadStyleAsync } from "./app"; -// Modules -export const bar = { - vertical: true, - modules: { - osIcon: { - enabled: true, - }, - activeWindow: { - enabled: true, - }, - mediaPlaying: { - enabled: true, +const CONFIG = `${GLib.get_user_config_dir()}/caelestia/shell.json`; + +const s = (v: T): Variable => Variable(v); + +const warn = (e: Error) => console.warn(`Invalid config: ${e}`); + +const updateSection = (from: { [k: string]: any }, to: { [k: string]: any }, path: string) => { + for (const [k, v] of Object.entries(from)) { + if (to.hasOwnProperty(k)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) updateSection(v, to[k], `${path}.${k}`); + else if (typeof v === typeof to[k].get()) to[k].set(v); + else console.warn(`Invalid type for ${path}.${k}: ${typeof v} != ${typeof to[k].get()}`); + } else console.warn(`Unknown config key: ${path}.${k}`); + } +}; + +export const updateConfig = async () => { + const conf: { [k: string]: any } = JSON.parse(await readFileAsync(CONFIG)); + for (const [k, v] of Object.entries(conf)) { + if (config.hasOwnProperty(k)) updateSection(v, config[k as keyof typeof config], k); + else console.warn(`Unknown config key: ${k}`); + } + loadStyleAsync().catch(console.error); +}; + +export const initConfig = () => { + monitorFile(CONFIG, () => updateConfig().catch(warn)); + updateConfig().catch(warn); +}; + +const config = { + // Modules + bar: { + vertical: s(true), + modules: { + osIcon: { + enabled: s(true), + }, + activeWindow: { + enabled: s(true), + }, + mediaPlaying: { + enabled: s(true), + }, + workspaces: { + enabled: s(true), + shown: s(5), + }, + tray: { + enabled: s(true), + }, + statusIcons: { + enabled: s(true), + }, + pkgUpdates: { + enabled: s(true), + }, + notifCount: { + enabled: s(true), + }, + battery: { + enabled: s(true), + }, + dateTime: { + enabled: s(true), + format: s("%d/%m/%y %R"), + detailedFormat: s("%c"), + }, + power: { + enabled: s(true), + }, }, - workspaces: { - enabled: true, - shown: 5, + }, + launcher: { + maxResults: s(15), // Max shown results at one time (i.e. max height of the launcher) + apps: { + maxResults: s(30), // Actual max results, -1 for infinite + pins: s([ + ["zen", "firefox", "waterfox", "google-chrome", "chromium", "brave-browser"], + ["foot", "alacritty", "kitty", "wezterm"], + ["thunar", "nemo", "nautilus"], + ["codium", "code", "clion", "intellij-idea-ultimate-edition"], + ["spotify-adblock", "spotify", "audacious", "elisa"], + ]), }, - tray: { - enabled: true, + files: { + maxResults: s(40), // Actual max results, -1 for infinite + fdOpts: s(["-a", "-t", "f"]), // Options to pass to `fd` }, - statusIcons: { - enabled: true, + math: { + maxResults: s(40), // Actual max results, -1 for infinite }, - pkgUpdates: { - enabled: true, + windows: { + maxResults: s(-1), // Actual max results, -1 for infinite + weights: { + // Weights for fuzzy sort + title: s(1), + class: s(1), + initialTitle: s(0.5), + initialClass: s(0.5), + }, }, - notifCount: { - enabled: true, + todo: { + notify: s(true), }, - battery: { - enabled: true, + }, + notifpopups: { + maxPopups: s(-1), + expire: s(false), + agoTime: s(true), // Whether to show time in ago format, e.g. 10 mins ago, or raw time, e.g. 10:42 + }, + osds: { + volume: { + position: s(Astal.WindowAnchor.RIGHT), + margin: s(20), + hideDelay: s(1500), + showValue: s(true), }, - dateTime: { - enabled: true, - format: "%d/%m/%y %R", - detailedFormat: "%c", + brightness: { + position: s(Astal.WindowAnchor.LEFT), + margin: s(20), + hideDelay: s(1500), + showValue: s(true), }, - power: { - enabled: true, + lock: { + spacing: s(5), + caps: { + hideDelay: s(1000), + }, + num: { + hideDelay: s(1000), + }, }, }, -}; - -export const launcher = { - maxResults: 15, // Max shown results at one time (i.e. max height of the launcher) - apps: { - maxResults: 30, // Actual max results, -1 for infinite - pins: [ - ["firefox", "waterfox", "google-chrome", "chromium", "brave-browser", "vivaldi-stable", "vivaldi-snapshot"], - ["foot", "alacritty", "kitty", "wezterm"], - ["thunar", "nemo", "nautilus"], - ["codium", "code", "clion", "intellij-idea-ultimate-edition"], - ["spotify-adblock", "spotify", "audacious", "elisa"], - ], - }, - files: { - maxResults: 40, // Actual max results, -1 for infinite - fdOpts: ["-a", "-t", "f"], // Options to pass to `fd` - }, + // Services math: { - maxResults: 40, // Actual max results, -1 for infinite + maxHistory: 100, }, - windows: { - maxResults: -1, // Actual max results, -1 for infinite - weights: { - // Weights for fuzzy sort - title: 1, - class: 1, - initialTitle: 0.5, - initialClass: 0.5, - }, + updates: { + interval: 900000, }, - todo: { - notify: true, + weather: { + interval: 600000, + key: "assets/weather-api-key.txt", // Path to file containing api key relative to the base directory. To get a key, visit https://weatherapi.com/ + location: "", // Location as a string or empty to autodetect + imperial: false, }, -}; - -export const notifpopups = { - maxPopups: -1, - expire: false, - agoTime: true, // Whether to show time in ago format, e.g. 10 mins ago, or raw time, e.g. 10:42 -}; - -export const osds = { - volume: { - position: Astal.WindowAnchor.RIGHT, - margin: 20, - hideDelay: 1500, - showValue: true, + cpu: { + interval: 2000, }, - brightness: { - position: Astal.WindowAnchor.LEFT, - margin: 20, - hideDelay: 1500, - showValue: true, + gpu: { + interval: 2000, }, - lock: { - spacing: 5, - caps: { - hideDelay: 1000, - }, - num: { - hideDelay: 1000, - }, + memory: { + interval: 5000, + }, + storage: { + interval: 5000, }, }; -// Services -export const math = { - maxHistory: 100, -}; - -export const updates = { - interval: 900000, -}; - -export const weather = { - interval: 600000, - key: "assets/weather-api-key.txt", // Path to file containing api key relative to the base directory. To get a key, visit https://weatherapi.com/ - location: "", // Location as a string or empty to autodetect - imperial: false, -}; - -export const cpu = { - interval: 2000, -}; - -export const gpu = { - interval: 2000, -}; - -export const memory = { - interval: 5000, -}; - -export const storage = { - interval: 5000, -}; +export const { bar, launcher, notifpopups, osds, math, updates, weather, cpu, gpu, memory, storage } = config; diff --git a/src/modules/bar.tsx b/src/modules/bar.tsx index cf3ce5e..c1c3dfc 100644 --- a/src/modules/bar.tsx +++ b/src/modules/bar.tsx @@ -8,7 +8,7 @@ import type { AstalWidget } from "@/utils/types"; import { setupCustomTooltip } from "@/utils/widgets"; import type PopupWindow from "@/widgets/popupwindow"; import { execAsync, Variable } from "astal"; -import { bind, kebabify } from "astal/binding"; +import Binding, { bind, kebabify } from "astal/binding"; import { App, Astal, Gtk } from "astal/gtk3"; import { bar as config } from "config"; import AstalBattery from "gi://AstalBattery"; @@ -17,7 +17,7 @@ import AstalHyprland from "gi://AstalHyprland"; import AstalNetwork from "gi://AstalNetwork"; import AstalNotifd from "gi://AstalNotifd"; import AstalTray from "gi://AstalTray"; -import AstalWp01 from "gi://AstalWp"; +import AstalWp from "gi://AstalWp"; const hyprland = AstalHyprland.get_default(); @@ -83,7 +83,7 @@ const OSIcon = () => ( const ActiveWindow = () => ( { const title = Variable(""); @@ -109,14 +109,15 @@ const ActiveWindow = () => ( } /> ); @@ -139,7 +140,7 @@ const MediaPlaying = () => { setupCustomTooltip(self, bind(label)); }} > - + players.hookLastPlayer(self, "notify::identity", () => { @@ -155,12 +156,14 @@ const MediaPlaying = () => { } /> @@ -169,8 +172,8 @@ const MediaPlaying = () => { const Workspace = ({ idx }: { idx: number }) => { let wsId = hyprland.focusedWorkspace - ? Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown) * - config.modules.workspaces.shown + + ? Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown.get()) * + config.modules.workspaces.shown.get() + idx : idx; return ( @@ -184,16 +187,18 @@ const Workspace = ({ idx }: { idx: number }) => { "occupied", hyprland.clients.some(c => c.workspace?.id === wsId) ); - - self.hook(hyprland, "notify::focused-workspace", () => { + const updateWs = () => { if (!hyprland.focusedWorkspace) return; wsId = - Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown) * - config.modules.workspaces.shown + + Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown.get()) * + config.modules.workspaces.shown.get() + idx; self.toggleClassName("focused", hyprland.focusedWorkspace.id === wsId); update(); - }); + }; + + self.hook(config.modules.workspaces.shown, updateWs); + self.hook(hyprland, "notify::focused-workspace", () => updateWs); self.hook(hyprland, "client-added", update); self.hook(hyprland, "client-moved", update); self.hook(hyprland, "client-removed", update); @@ -214,10 +219,10 @@ const Workspaces = () => ( hyprland.dispatch("workspace", (event.delta_y < 0 ? "-" : "+") + 1); }} > - - {Array.from({ length: config.modules.workspaces.shown }).map((_, idx) => ( - // Start from 1 - ))} + + {bind(config.modules.workspaces.shown).as( + n => Array.from({ length: n }).map((_, idx) => ) // Start from 1 + )} ); @@ -226,7 +231,7 @@ const TrayItem = (item: AstalTray.TrayItem) => ( event.get_button()[1] === Astal.MouseButton.SECONDARY && item.activate(0, 0)} usePopover={false} - direction={config.vertical ? Gtk.ArrowType.RIGHT : Gtk.ArrowType.DOWN} + direction={bind(config.vertical).as(v => (v ? Gtk.ArrowType.RIGHT : Gtk.ArrowType.DOWN))} menuModel={bind(item, "menuModel")} actionGroup={bind(item, "actionGroup").as(a => ["dbusmenu", a])} setup={self => setupCustomTooltip(self, bind(item, "tooltipMarkup"))} @@ -237,7 +242,7 @@ const TrayItem = (item: AstalTray.TrayItem) => ( const Tray = () => ( i.length > 0)} > @@ -370,7 +375,7 @@ const BluetoothDevice = (device: AstalBluetooth.Device) => ( ); const Bluetooth = () => ( - + ); @@ -501,7 +515,9 @@ const DateTime = () => ( const DateTimeVertical = () => (