summaryrefslogtreecommitdiff
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/apps.ts3
-rw-r--r--services/math.ts151
-rw-r--r--services/monitors.ts127
-rw-r--r--services/players.ts154
-rw-r--r--services/updates.ts148
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();
- }
-}