diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-01-16 16:35:37 +1100 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-01-16 16:35:37 +1100 |
| commit | 02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38 (patch) | |
| tree | 5e2a56becf6ba6961995e541ce9688224f704773 /services | |
| parent | popupwindow: switch to class (diff) | |
| download | caelestia-shell-02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38.tar.gz caelestia-shell-02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38.tar.bz2 caelestia-shell-02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38.zip | |
refactor: move ts to src
Also move popupwindow to own file
Diffstat (limited to 'services')
| -rw-r--r-- | services/apps.ts | 3 | ||||
| -rw-r--r-- | services/math.ts | 151 | ||||
| -rw-r--r-- | services/monitors.ts | 127 | ||||
| -rw-r--r-- | services/players.ts | 154 | ||||
| -rw-r--r-- | services/updates.ts | 148 |
5 files changed, 0 insertions, 583 deletions
diff --git a/services/apps.ts b/services/apps.ts deleted file mode 100644 index 5396ac7..0000000 --- a/services/apps.ts +++ /dev/null @@ -1,3 +0,0 @@ -import AstalApps from "gi://AstalApps"; - -export const Apps = new AstalApps.Apps(); diff --git a/services/math.ts b/services/math.ts deleted file mode 100644 index c66798c..0000000 --- a/services/math.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { GLib, GObject, property, readFile, register, writeFileAsync } from "astal"; -import { derivative, evaluate, rationalize, simplify } from "mathjs/number"; - -export interface HistoryItem { - equation: string; - result: string; - icon: string; -} - -@register({ GTypeName: "Math" }) -export default class Math extends GObject.Object { - static instance: Math; - static get_default() { - if (!this.instance) this.instance = new Math(); - - return this.instance; - } - - readonly #maxHistory = 20; - readonly #path = `${CACHE}/math-history.json`; - readonly #history: HistoryItem[] = []; - - #variables: Record<string, string> = {}; - #lastExpression: HistoryItem | null = null; - - @property(Object) - get history() { - return this.#history; - } - - #save() { - writeFileAsync(this.#path, JSON.stringify(this.#history)).catch(console.error); - } - - /** - * Commits the last evaluated expression to the history - */ - commit() { - if (!this.#lastExpression) return; - - // Try select first to prevent duplicates, if it fails, add it - if (!this.select(this.#lastExpression)) { - this.#history.unshift(this.#lastExpression); - if (this.#history.length > this.#maxHistory) this.#history.pop(); - this.notify("history"); - this.#save(); - } - this.#lastExpression = null; - } - - /** - * Moves an item in the history to the top - * @param item The item to select - * @returns If the item was successfully selected - */ - select(item: HistoryItem) { - const idx = this.#history.findIndex(i => i.equation === item.equation && i.result === item.result); - if (idx >= 0) { - this.#history.splice(idx, 1); - this.#history.unshift(item); - this.notify("history"); - this.#save(); - - return true; - } - - return false; - } - - /** - * Clears the history and variables - */ - clear() { - if (this.#history.length > 0) { - this.#history.length = 0; - this.notify("history"); - this.#save(); - } - this.#lastExpression = null; - this.#variables = {}; - } - - /** - * Evaluates an equation and adds it to the history - * @param equation The equation to evaluate - * @returns A {@link HistoryItem} representing the result of the equation - */ - evaluate(equation: string): HistoryItem { - if (equation.startsWith("clear")) - return { - equation: "Clear history", - result: "Delete history and previously set variables", - icon: "delete_forever", - }; - - let result: string, icon: string; - try { - if (equation.startsWith("help")) { - equation = "Help"; - result = - "This is a calculator powered by Math.js.\nAvailable functions:\n\thelp: show help\n\tclear: clear history\n\t<x> = <equation>: sets <x> to <equation>\n\tsimplify <equation>: simplifies <equation>\n\tderive <x> <equation>: derives <equation> with respect to <x>\n\tdd<x> <equation>: short form of derive\n\trationalize <equation>: rationalizes <equation>\n\t<equation>: evaluates <equation>\nSee the documentation for syntax and inbuilt functions."; - icon = "help"; - } else if (equation.includes("=")) { - const [left, right] = equation.split("="); - try { - this.#variables[left.trim()] = simplify(right).toString(); - } catch { - this.#variables[left.trim()] = right.trim(); - } - result = this.#variables[left.trim()]; - icon = "equal"; - } else if (equation.startsWith("simplify")) { - result = simplify(equation.slice(8), this.#variables).toString(); - icon = "function"; - } else if (equation.startsWith("derive") || equation.startsWith("dd")) { - const isShortForm = equation.startsWith("dd"); - const respectTo = isShortForm ? equation.split(" ")[0].slice(2) : equation.split(" ")[1]; - if (!respectTo) throw new Error(`Format: ${isShortForm ? "dd" : "derive "}<respect-to> <equation>`); - result = derivative(equation.slice((isShortForm ? 2 : 7) + respectTo.length), respectTo).toString(); - icon = "function"; - } else if (equation.startsWith("rationalize")) { - result = rationalize(equation.slice(11), this.#variables).toString(); - icon = "function"; - } else { - result = evaluate(equation, this.#variables).toString(); - icon = "calculate"; - } - } catch (e) { - equation = "Invalid equation: " + equation; - result = String(e); - icon = "error"; - } - - return (this.#lastExpression = { equation, result, icon }); - } - - constructor() { - super(); - - // Load history - if (GLib.file_test(this.#path, GLib.FileTest.EXISTS)) { - try { - this.#history = JSON.parse(readFile(this.#path)); - // Init eval to create variables and last expression - for (const item of this.#history) this.evaluate(item.equation); - } catch (e) { - console.error("Math - Unable to load history", e); - } - } - } -} diff --git a/services/monitors.ts b/services/monitors.ts deleted file mode 100644 index 78a0161..0000000 --- a/services/monitors.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { GObject, execAsync, property, register } from "astal"; -import AstalHyprland from "gi://AstalHyprland"; - -@register({ GTypeName: "Monitor" }) -export class Monitor extends GObject.Object { - readonly monitor: AstalHyprland.Monitor; - readonly width: number; - readonly height: number; - readonly id: number; - readonly serial: string; - readonly name: string; - readonly description: string; - - @property(AstalHyprland.Workspace) - get activeWorkspace() { - return this.monitor.activeWorkspace; - } - - isDdc: boolean = false; - busNum?: string; - - #brightness: number = 0; - - @property(Number) - get brightness() { - return this.#brightness; - } - - set brightness(value) { - value = Math.min(1, Math.max(0, value)); - - this.#brightness = value; - this.notify("brightness"); - execAsync( - this.isDdc - ? `ddcutil -b ${this.busNum} setvcp 10 ${Math.round(value * 100)}` - : `brightnessctl set ${Math.floor(value * 100)}% -q` - ).catch(console.error); - } - - constructor(monitor: AstalHyprland.Monitor) { - super(); - - this.monitor = monitor; - this.width = monitor.width; - this.height = monitor.height; - this.id = monitor.id; - this.serial = monitor.serial; - this.name = monitor.name; - this.description = monitor.description; - - monitor.connect("notify::active-workspace", () => this.notify("active-workspace")); - - execAsync("ddcutil detect --brief") - .then(out => { - this.isDdc = out.split("\n\n").some(display => { - if (!/^Display \d+/.test(display)) return false; - const lines = display.split("\n"); - if (lines[3].split(":")[3] !== monitor.serial) return false; - this.busNum = lines[1].split("/dev/i2c-")[1]; - return true; - }); - }) - .catch(() => (this.isDdc = false)) - .finally(async () => { - if (this.isDdc) { - const info = (await execAsync(`ddcutil -b ${this.busNum} getvcp 10 --brief`)).split(" "); - this.#brightness = Number(info[3]) / Number(info[4]); - } else - this.#brightness = - Number(await execAsync("brightnessctl get")) / Number(await execAsync("brightnessctl max")); - }); - } -} - -@register({ GTypeName: "Monitors" }) -export default class Monitors extends GObject.Object { - static instance: Monitors; - static get_default() { - if (!this.instance) this.instance = new Monitors(); - - return this.instance; - } - - readonly #map: Map<number, Monitor> = new Map(); - - @property(Object) - get map() { - return this.#map; - } - - @property(Object) - get list() { - return Array.from(this.#map.values()); - } - - @property(Monitor) - get active() { - return this.#map.get(AstalHyprland.get_default().focusedMonitor.id)!; - } - - #notify() { - this.notify("map"); - this.notify("list"); - } - - forEach(fn: (monitor: Monitor) => void) { - for (const monitor of this.#map.values()) fn(monitor); - } - - constructor() { - super(); - - const hyprland = AstalHyprland.get_default(); - - for (const monitor of hyprland.monitors) this.#map.set(monitor.id, new Monitor(monitor)); - if (this.#map.size > 0) this.#notify(); - - hyprland.connect("monitor-added", (_, monitor) => { - this.#map.set(monitor.id, new Monitor(monitor)); - this.#notify(); - }); - hyprland.connect("monitor-removed", (_, id) => this.#map.delete(id) && this.#notify()); - - hyprland.connect("notify::focused-monitor", () => this.notify("active")); - } -} diff --git a/services/players.ts b/services/players.ts deleted file mode 100644 index b81d4b5..0000000 --- a/services/players.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { GLib, GObject, property, readFile, register, writeFileAsync } from "astal"; -import AstalMpris from "gi://AstalMpris"; -import { isRealPlayer } from "../utils/mpris"; - -@register({ GTypeName: "Players" }) -export default class Players extends GObject.Object { - static instance: Players; - static get_default() { - if (!this.instance) this.instance = new Players(); - - return this.instance; - } - - readonly #path = `${CACHE}/players.txt`; - readonly #players: AstalMpris.Player[] = []; - readonly #subs = new Map< - JSX.Element, - { signals: string[]; callback: () => void; ids: number[]; player: AstalMpris.Player | null } - >(); - - @property(AstalMpris.Player) - get lastPlayer(): AstalMpris.Player | null { - return this.#players.length > 0 && this.#players[0].identity !== null ? this.#players[0] : null; - } - - /** - * List of real players. - */ - @property(Object) - get list() { - return this.#players; - } - - hookLastPlayer(widget: JSX.Element, signal: string, callback: () => void): this; - hookLastPlayer(widget: JSX.Element, signals: string[], callback: () => void): this; - hookLastPlayer(widget: JSX.Element, signals: string | string[], callback: () => void) { - if (!Array.isArray(signals)) signals = [signals]; - // Add subscription - if (this.lastPlayer) - this.#subs.set(widget, { - signals, - callback, - ids: signals.map(s => this.lastPlayer!.connect(s, callback)), - player: this.lastPlayer, - }); - else this.#subs.set(widget, { signals, callback, ids: [], player: null }); - - // Remove subscription on widget destroyed - widget.connect("destroy", () => { - const sub = this.#subs.get(widget); - if (sub?.player) sub.ids.forEach(id => sub.player!.disconnect(id)); - this.#subs.delete(widget); - }); - - // Initial run of callback - callback(); - - // For chaining - return this; - } - - makeCurrent(player: AstalMpris.Player) { - const index = this.#players.indexOf(player); - // Ignore if already current - if (index === 0) return; - // Remove if present - else if (index > 0) this.#players.splice(index, 1); - // Connect signals if not already in list (i.e. new player) - else this.#connectPlayerSignals(player); - - // Add to front - this.#players.unshift(player); - this.#updatePlayer(); - - // Save to file - this.#save(); - } - - #updatePlayer() { - this.notify("last-player"); - - for (const sub of this.#subs.values()) { - sub.callback(); - if (sub.player) sub.ids.forEach(id => sub.player!.disconnect(id)); - sub.ids = this.lastPlayer ? sub.signals.map(s => this.lastPlayer!.connect(s, sub.callback)) : []; - sub.player = this.lastPlayer; - } - } - - #save() { - writeFileAsync(this.#path, this.#players.map(p => p.busName).join("\n")).catch(console.error); - } - - #connectPlayerSignals(player: AstalMpris.Player) { - // Change order on attribute change - for (const signal of [ - "notify::playback-status", - "notify::shuffle-status", - "notify::loop-status", - "notify::volume", - "notify::rate", - ]) - player.connect(signal, () => this.makeCurrent(player)); - } - - constructor() { - super(); - - const mpris = AstalMpris.get_default(); - - // Load players - if (GLib.file_test(this.#path, GLib.FileTest.EXISTS)) { - this.#players = readFile(this.#path) - .split("\n") - .map(p => mpris.players.find(p2 => p2.busName === p)) - .filter(isRealPlayer) as AstalMpris.Player[]; - // Add new players from in between sessions - for (const player of mpris.players) - if (!this.#players.includes(player) && isRealPlayer(player)) this.#players.push(player); - } else { - const sortOrder = [ - AstalMpris.PlaybackStatus.PLAYING, - AstalMpris.PlaybackStatus.PAUSED, - AstalMpris.PlaybackStatus.STOPPED, - ]; - this.#players = mpris.players - .filter(isRealPlayer) - .sort((a, b) => sortOrder.indexOf(a.playbackStatus) - sortOrder.indexOf(b.playbackStatus)); - } - this.#updatePlayer(); - this.#save(); - // Connect signals to loaded players - for (const player of this.#players) this.#connectPlayerSignals(player); - - // Add and connect signals when added - mpris.connect("player-added", (_, player) => { - if (isRealPlayer(player)) { - this.makeCurrent(player); - this.notify("list"); - } - }); - - // Remove when closed - mpris.connect("player-closed", (_, player) => { - const index = this.#players.indexOf(player); - if (index >= 0) { - this.#players.splice(index, 1); - this.notify("list"); - if (index === 0) this.#updatePlayer(); - this.#save(); - } - }); - } -} diff --git a/services/updates.ts b/services/updates.ts deleted file mode 100644 index 0b04e85..0000000 --- a/services/updates.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { execAsync, GLib, GObject, property, readFileAsync, register, writeFileAsync } from "astal"; -import { updates as config } from "../config"; - -interface Update { - name: string; - full: string; -} - -interface Repo { - repo?: string[]; - updates: Update[]; - icon: string; - name: string; -} - -interface Data { - cached?: boolean; - repos: Repo[]; - errors: string[]; -} - -@register({ GTypeName: "Updates" }) -export default class Updates extends GObject.Object { - static instance: Updates; - static get_default() { - if (!this.instance) this.instance = new Updates(); - - return this.instance; - } - - readonly #cachePath = `${CACHE}/updates.txt`; - - #timeout?: GLib.Source; - #loading = false; - #data: Data = { cached: true, repos: [], errors: [] }; - - @property(Boolean) - get loading() { - return this.#loading; - } - - @property(Object) - get data() { - return this.#data; - } - - @property(Object) - get list() { - return this.#data.repos.map(r => r.updates).flat(); - } - - @property(Number) - get numUpdates() { - return this.#data.repos.reduce((acc, repo) => acc + repo.updates.length, 0); - } - - async #updateFromCache() { - this.#data = JSON.parse(await readFileAsync(this.#cachePath)); - this.notify("data"); - this.notify("list"); - this.notify("num-updates"); - } - - async #getRepo(repo: string) { - return (await execAsync(`bash -c "comm -12 <(pacman -Qq | sort) <(pacman -Slq '${repo}' | sort)"`)).split("\n"); - } - - getUpdates() { - // Return if already getting updates - if (this.#loading) return; - - this.#loading = true; - this.notify("loading"); - - // Get new updates - Promise.allSettled([execAsync("checkupdates"), execAsync("yay -Qua")]) - .then(async ([pacman, yay]) => { - const data: Data = { repos: [], errors: [] }; - - // Pacman updates (checkupdates) - if (pacman.status === "fulfilled") { - const repos: Repo[] = [ - { repo: await this.#getRepo("core"), updates: [], icon: "hub", name: "Core repository" }, - { - repo: await this.#getRepo("extra"), - updates: [], - icon: "add_circle", - name: "Extra repository", - }, - { - repo: await this.#getRepo("multilib"), - updates: [], - icon: "account_tree", - name: "Multilib repository", - }, - ]; - - for (const update of pacman.value.split("\n")) { - const pkg = update.split(" ")[0]; - for (const repo of repos) - if (repo.repo?.includes(pkg)) repo.updates.push({ name: pkg, full: update }); - } - - for (const repo of repos) if (repo.updates.length > 0) data.repos.push(repo); - } - - // AUR and devel updates (yay -Qua) - if (yay.status === "fulfilled") { - const aur: Repo = { updates: [], icon: "deployed_code_account", name: "AUR" }; - - for (const update of yay.value.split("\n")) { - if (/^\s*->/.test(update)) data.errors.push(update); // Error - else aur.updates.push({ name: update.split(" ")[0], full: update }); - } - - if (aur.updates.length > 0) data.repos.push(aur); - } - - if (data.errors.length > 0 && data.repos.length === 0) { - this.#updateFromCache().catch(console.error); - } else { - // Cache and set - writeFileAsync(this.#cachePath, JSON.stringify({ cached: true, ...data })).catch(console.error); - this.#data = data; - this.notify("data"); - this.notify("list"); - this.notify("num-updates"); - } - - this.#loading = false; - this.notify("loading"); - - this.#timeout?.destroy(); - this.#timeout = setTimeout(() => this.getUpdates(), config.interval); - }) - .catch(console.error); - } - - constructor() { - super(); - - // Initial update from cache, if fail then write valid data to cache so future reads don't fail - this.#updateFromCache().catch(() => - writeFileAsync(this.#cachePath, JSON.stringify(this.#data)).catch(console.error) - ); - this.getUpdates(); - } -} |