diff --git a/ags/app.ts b/ags/app.ts index 88c68a4..7a14af7 100644 --- a/ags/app.ts +++ b/ags/app.ts @@ -19,6 +19,70 @@ import { Scope } from "/usr/share/ags/js/gnim/src/jsx/scope"; import App from "ags/gtk4/app" import GObject from "ags/gobject"; import AstalNotifd from "gi://AstalNotifd"; +import GLib from "gi://GLib?version=2.0"; + + +type ConfigEntries = { + workspaces?: Partial<{ + /** this is the function that shows the Workspace's IDs + * around the current workspace if one breaks the crescent order. + * It basically helps keyboard navigation between workspaces. + * --- + * Example: 1(empty, current, shows ID), 2(empty, does not appear(makes + * the previous not to be in a crescent order)), 3(not empty, shows ID) */ + enable_helper: boolean; + /** breaks `enable_helper`, makes all workspaces show their respective ID + * by default */ + always_show_id: boolean; + }>; + + clock?: Partial<{ + /** use the same format as gnu's `date` command */ + date_format: string; + }>; + + notifications?: Partial<{ + timeout_low: number; + timeout_normal: number; + timeout_critical: number; + }>; + + night_light?: Partial<{ + /** whether to save night light values to disk */ + save_on_shutdown: boolean; + }>; + + misc?: Partial<{ + play_bell_on_volume_change: boolean; + }>; +}; + +export const generalConfig = new Config( + `${GLib.get_user_config_dir()}/colorshell/config.json`, { + notifications: { + timeout_low: 4000, + timeout_normal: 6000, + timeout_critical: 0 + }, + + night_light: { + save_on_shutdown: true + }, + + workspaces: { + always_show_id: false, + enable_helper: true + }, + + clock: { + date_format: "%A %d, %H:%M" + }, + + misc: { + play_bell_on_volume_change: true + } + } +); export const appScope: Scope = new Scope(null); @@ -46,9 +110,6 @@ App.start({ console.log(`Colorshell: initialized instance as: "${ App.instanceName || "astal" }"`); connections.set(App, App.connect("shutdown", () => appScope.dispose())); - console.log("Config: initializing configuration file"); - Config.getDefault(); - Stylesheet.getDefault().compileApply(); // Init clipboard module diff --git a/ags/scripts/arg-handler.ts b/ags/scripts/arg-handler.ts index 988c786..fc65e40 100644 --- a/ags/scripts/arg-handler.ts +++ b/ags/scripts/arg-handler.ts @@ -5,8 +5,8 @@ import { timeout } from "ags/time"; import { Runner } from "../runner/Runner"; import { showWorkspaceNumber } from "../widget/bar/Workspaces"; import { playSystemBell } from "./utils"; -import { Config } from "./config"; import { player, setPlayer } from "../widget/bar/Media"; +import { generalConfig } from "../app"; import AstalIO from "gi://AstalIO"; import GLib from "gi://GLib?version=2.0"; @@ -236,7 +236,7 @@ function handleVolumeArgs(args: Array) { Wireplumber.getDefault().increaseSinkVolume(Number.parseInt(args[2])) : Wireplumber.getDefault().increaseSourceVolume(Number.parseInt(args[2])) - Config.getDefault().getProperty("misc.play_bell_on_volume_change", "boolean") === true && + generalConfig.getProperty("misc.play_bell_on_volume_change", "boolean") === true && playSystemBell(); return `Done increasing volume by ${args[2]}`; @@ -246,7 +246,7 @@ function handleVolumeArgs(args: Array) { Wireplumber.getDefault().decreaseSinkVolume(Number.parseInt(args[2])) : Wireplumber.getDefault().decreaseSourceVolume(Number.parseInt(args[2])) - Config.getDefault().getProperty("misc.play_bell_on_volume_change", "boolean") === true && + generalConfig.getProperty("misc.play_bell_on_volume_change", "boolean") === true && playSystemBell(); return `Done decreasing volume to ${args[2]}`; diff --git a/ags/scripts/config.ts b/ags/scripts/config.ts index 2dec5d9..4e58440 100644 --- a/ags/scripts/config.ts +++ b/ags/scripts/config.ts @@ -12,99 +12,36 @@ import { Accessor } from "ags"; export { Config }; - -export type ConfigEntries = Partial<{ - workspaces: Partial<{ - /** this is the function that shows the Workspace's IDs - * around the current workspace if one breaks the crescent order. - * It basically helps keyboard navigation between workspaces. - * --- - * Example: 1(empty, current, shows ID), 2(empty, does not appear(makes - * the previous not to be in a crescent order)), 3(not empty, shows ID) */ - enable_helper: boolean; - /** breaks `enable_helper`, makes all workspaces show their respective ID - * by default */ - always_show_id: boolean; - }>; - - clock: Partial<{ - /** use the same format as gnu's `date` command */ - date_format: string; - }>; - - notifications: Partial<{ - timeout_low: number; - timeout_normal: number; - timeout_critical: number; - }>; - - night_light: Partial<{ - /** whether to save night light values to disk */ - save_on_shutdown: boolean; - }>; - - misc: Partial<{ - play_bell_on_volume_change: boolean; - }>; -}>; - type ValueTypes = "string" | "boolean" | "object" | "number" | "undefined" | "any"; -interface ConfigSignals extends GObject.Object.SignalSignatures { - "notify::entries": (entries: ConfigEntries) => void; -} - @register({ GTypeName: "Config" }) -class Config extends GObject.Object { - private static instance: Config; - - declare $signals: ConfigSignals; - - private readonly defaultFile = Gio.File.new_for_path( - `${GLib.get_user_config_dir()}/colorshell/config.json`); +class Config, V extends string|object|any> extends GObject.Object { + declare $signals: GObject.Object.SignalSignatures & { + "notify::entries": (entries: Record) => void; + }; /** unmodified object with default entries. User-values are stored * in the `entries` field */ - public readonly defaults: ConfigEntries = { - notifications: { - timeout_low: 4000, - timeout_normal: 6000, - timeout_critical: 0 - }, - - night_light: { - save_on_shutdown: true - }, - - workspaces: { - always_show_id: false, - enable_helper: true - }, - - clock: { - date_format: "%A %d, %H:%M" - }, - - misc: { - play_bell_on_volume_change: true - } - }; + public readonly defaults: Record; @getter(Object) - public get entries() { return this.#entries; } + public get entries(): object { return this.#entries; } #file: Gio.File; - #entries: ConfigEntries = this.defaults; + #entries: Record; private timeout: (AstalIO.Time|boolean|undefined); public get file() { return this.#file; }; - constructor(filePath?: (Gio.File|string)) { + constructor(filePath: Gio.File|string, defaults?: Record) { super(); + this.defaults = (defaults ?? {}) as Record; + this.#entries = { ...defaults } as Record; + this.#file = (typeof filePath === "string") ? Gio.File.new_for_path(filePath) - : (filePath ?? this.defaultFile); + : filePath; if(!this.#file.query_exists(null)) { this.#file.make_directory_with_parents(null); @@ -156,19 +93,12 @@ class Config extends GObject.Object { ); } - public static getDefault(): Config { - if(!this.instance) - this.instance = new Config(); - - return this.instance; - } - private async readFile(): Promise { await readFileAsync(this.#file.get_path()!).then((content) => { - let config: (ConfigEntries|undefined); + let config: (Record|undefined); try { - config = JSON.parse(content) as ConfigEntries; + config = JSON.parse(content) as Record; } catch(e) { Notifications.getDefault().sendNotification({ urgency: AstalNotifd.Urgency.NORMAL, @@ -189,7 +119,7 @@ class Config extends GObject.Object { return; // TODO needs more work, like object-recursive(infinite depth) entry attributions - this.entries[k as keyof typeof this.entries] = config[k as keyof typeof config]; + this.#entries[k as keyof Record] = config[k as keyof typeof config]; } this.notify("entries"); @@ -204,22 +134,22 @@ class Config extends GObject.Object { }); } - public bindProperty(propertyPath: (keyof ConfigEntries|string), expectType?: ValueTypes): Accessor { - return new Accessor(() => this.getProperty(propertyPath, expectType), (callback: () => void) => { + public bindProperty(propertyPath: string, expectType?: ValueTypes): Accessor { + return new Accessor>(() => this.getProperty(propertyPath, expectType), (callback: () => void) => { const id = this.connect("notify::entries", () => callback()); return () => this.disconnect(id); }); } public getProperty(path: string, expectType?: ValueTypes): (any|undefined) { - return this._getProperty(path, this.entries, expectType); + return this._getProperty(path, this.#entries, expectType); } public getPropertyDefault(path: string, expectType?: ValueTypes): (any|undefined) { return this._getProperty(path, this.defaults, expectType); } - private _getProperty(path: string, entries: ConfigEntries, expectType?: ValueTypes): (any|undefined) { + private _getProperty(path: string, entries: Record, expectType?: ValueTypes): (any|undefined) { let property: any = entries; const pathArray = path.split('.').filter(str => str); diff --git a/ags/scripts/notifications.ts b/ags/scripts/notifications.ts index 2a81ebd..d280e70 100644 --- a/ags/scripts/notifications.ts +++ b/ags/scripts/notifications.ts @@ -1,14 +1,14 @@ -import { Config } from "./config"; import { timeout } from "ags/time"; import { execAsync } from "ags/process"; - +import { readFile } from "ags/file"; +import { generalConfig } from "../app"; +import { onCleanup } from "ags"; import GObject, { getter, property, register, signal } from "ags/gobject"; + import AstalNotifd from "gi://AstalNotifd"; import AstalIO from "gi://AstalIO"; -import { onCleanup } from "ags"; import Gio from "gi://Gio?version=2.0"; import GLib from "gi://GLib?version=2.0"; -import { readFile } from "ags/file"; export interface HistoryNotification { @@ -53,7 +53,7 @@ class Notifications extends GObject.Object { this.#connections.push( AstalNotifd.get_default().connect("notified", (notifd, id) => { const notification = notifd.get_notification(id); - const notifTimeout = Config.getDefault().getProperty( + const notifTimeout = generalConfig.getProperty( `notifications.timeout_${this.getUrgencyString(notification.urgency).toLowerCase()}`, "number") as number; diff --git a/ags/widget/bar/Clock.tsx b/ags/widget/bar/Clock.tsx index bec076d..9d692a9 100644 --- a/ags/widget/bar/Clock.tsx +++ b/ags/widget/bar/Clock.tsx @@ -2,7 +2,8 @@ import { Gtk } from "ags/gtk4"; import { Windows } from "../../windows"; import { createBinding } from "ags"; import { time } from "../../scripts/utils"; -import { Config } from "../../scripts/config"; +import { generalConfig } from "../../app"; + export const Clock = () => @@ -14,7 +15,7 @@ export const Clock = () => ]; }} label={time((dt) => dt.format( - Config.getDefault().getProperty("clock.date_format", "string")) + generalConfig.getProperty("clock.date_format", "string")) ?? "An error occurred" )} />; diff --git a/ags/widget/bar/Workspaces.tsx b/ags/widget/bar/Workspaces.tsx index 3eebba6..a4eed41 100644 --- a/ags/widget/bar/Workspaces.tsx +++ b/ags/widget/bar/Workspaces.tsx @@ -1,12 +1,14 @@ import { Gtk } from "ags/gtk4"; import AstalHyprland from "gi://AstalHyprland"; import { getAppIcon, getSymbolicIcon } from "../../scripts/apps"; -import { Config } from "../../scripts/config"; import { Separator } from "../Separator"; +import { generalConfig } from "../../app"; import { createBinding, createComputed, createState, For, With } from "ags"; -import GObject from "gi://GObject?version=2.0"; import { variableToBoolean } from "../../scripts/utils"; +import GObject from "ags/gobject"; + + const [showNumbers, setShowNumbers] = createState(false); export const showWorkspaceNumber = (show: boolean) => setShowNumbers(show); @@ -83,8 +85,8 @@ export const Workspaces = () => { {(ws: AstalHyprland.Workspace, i) => { const showId = createComputed([ - Config.getDefault().bindProperty("workspaces.always_show_id", "boolean").as(Boolean), - Config.getDefault().bindProperty("workspaces.enable_helper", "boolean").as(Boolean), + generalConfig.bindProperty("workspaces.always_show_id", "boolean").as(Boolean), + generalConfig.bindProperty("workspaces.enable_helper", "boolean").as(Boolean), showNumbers, i ], (alwaysShowIds, enableHelper, showIds, i) => { diff --git a/ags/window/LogoutMenu.tsx b/ags/window/LogoutMenu.tsx index 1f19692..c459088 100644 --- a/ags/window/LogoutMenu.tsx +++ b/ags/window/LogoutMenu.tsx @@ -1,14 +1,14 @@ import { Astal, Gdk, Gtk } from "ags/gtk4"; import { execAsync } from "ags/process"; +import { generalConfig } from "../app"; import { AskPopup } from "../widget/AskPopup"; import { Notifications } from "../scripts/notifications"; import { NightLight } from "../scripts/nightlight"; -import { Config } from "../scripts/config"; import { time } from "../scripts/utils"; +import GObject from "ags/gobject"; import AstalNotifd from "gi://AstalNotifd"; import Gio from "gi://Gio?version=2.0"; -import GObject from "gi://GObject?version=2.0"; const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor; @@ -64,7 +64,7 @@ export const LogoutMenu = (mon: number) => title: "Power Off", text: "Are you sure you want to power off? Unsaved work will be lost.", onAccept: () => { - Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") && + generalConfig.getProperty("night_light.save_on_shutdown", "boolean") && NightLight.getDefault().saveData(); execAsync("systemctl poweroff"); @@ -76,7 +76,7 @@ export const LogoutMenu = (mon: number) => title: "Reboot", text: "Are you sure you want to Reboot? Unsaved work will be lost.", onAccept: () => { - Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") && + generalConfig.getProperty("night_light.save_on_shutdown", "boolean") && NightLight.getDefault().saveData(); execAsync("systemctl reboot"); @@ -95,7 +95,7 @@ export const LogoutMenu = (mon: number) => title: "Log out", text: "Are you sure you want to log out? Your session will be ended.", onAccept: () => { - Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") && + generalConfig.getProperty("night_light.save_on_shutdown", "boolean") && NightLight.getDefault().saveData(); execAsync(`hyprctl dispatch exit`).catch((err: Gio.IOErrorEnum) =>