// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. .pragma library // Pure helpers for the user page, tested by test_user.js under node. // `userctl get` output into an object. function parse(text) { const out = {}; for (const line of text.split("\n")) { const i = line.indexOf("="); if (i > 0) out[line.slice(0, i)] = line.slice(i + 1); } return out; } // `userctl keys` output into [{fpr, uid}]. function keys(text) { return text.split("\n").filter(l => l.includes("\t")) .map(l => ({ fpr: l.slice(0, l.indexOf("\t")), uid: l.slice(l.indexOf("\t") + 1) })); } // `locale -a` prints it_IT.utf8; AccountsService stores it_IT.UTF-8. function locales(text) { return text.split("\n") .filter(l => /\.utf-?8$/i.test(l) && !/^C\./.test(l)) .map(l => l.replace(/\.utf-?8$/i, ".UTF-8")) .sort(); } // The name git gets, for the page's preview. ponytail: duplicates userctl's // gitname; both tests pin the same cases, keep them in step. function gitName(name) { const w = name.trim().split(/\s+/).filter(s => s !== ""); if (w.length < 2) return w.join(" "); return w.slice(0, -1).join(" ") + " " + Array.from(w[w.length - 1])[0] + "."; } // Nominatim jsonv2 search results as location labels, "place, region, // country", deduplicated. Anything that is not a JSON array (an error page, // a rate-limit notice) is no suggestions rather than an error. function places(text) { let rows; try { rows = JSON.parse(text); } catch (e) { return []; } if (!Array.isArray(rows)) return []; const out = []; for (const r of rows) { const a = r.address || {}; const parts = [a.city || a.town || a.village || a.hamlet || a.municipality || r.name, a.state, a.country].filter(p => p); const label = parts.filter((p, i) => parts.indexOf(p) === i).join(", "); if (label && !out.includes(label)) out.push(label); } return out; } // The drawer's greeting, from ~/.config/quickshell/greetings: one per line, // {user} for the login name, blank lines and # comments skipped. A missing or // empty file is the built-in set. The row is rebuilt on every drawer open, so // the last pick lives here, in the shared library, to never show the same one // twice running. const defaultGreetings = ["Hello {user}", "Hi {user}", "Welcome back, {user}", "Hey there, {user}", "Good to see you, {user}"]; let lastGreeting = -1; function greeting(text, user, random) { const lines = text.split("\n").map(l => l.trim()).filter(l => l && !l.startsWith("#")); const all = lines.length ? lines : defaultGreetings; const skip = all.length > 1 && lastGreeting >= 0 && lastGreeting < all.length; let i = Math.floor((random ?? Math.random()) * (all.length - skip)); if (skip && i >= lastGreeting) i++; lastGreeting = i; return all[i].replace(/\{user\}/g, user); }