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 = () => (
-
+