Add AGS package manager

This commit is contained in:
Pascal
2026-04-28 04:24:08 +02:00
parent 6eb922c417
commit b4e4081f25
4 changed files with 480 additions and 0 deletions

View File

@@ -0,0 +1,314 @@
import app from "ags/gtk4/app";
import { Astal, Gtk } from "ags/gtk4";
import { execAsync } from "ags/process";
import css from "./package-manager.css";
const WINDOW_MARGIN_TOP = 48;
const ESC_KEYVAL = 65307;
type Helper = "pacman" | "paru";
type PackageResult = {
repo: string;
name: string;
version: string;
installed: boolean;
description: string;
};
let helper: Helper = "paru";
let query = "";
let results: PackageResult[] = [];
let busy = false;
let statusMessage = "Suchbegriff eingeben und Enter druecken.";
let errorMessage = "";
function shQuote(value: string) {
return `'${value.replace(/'/g, `'\\''`)}'`;
}
function notify(message: string) {
execAsync(["notify-send", "Pakete", message]).catch(console.error);
}
function runShell(command: string) {
return execAsync(["bash", "-lc", command]);
}
function setBusy(value: boolean) {
busy = value;
rebuild();
}
function helperAvailable(nextHelper: Helper) {
return runShell(`command -v ${nextHelper} >/dev/null 2>&1`);
}
function parseSearch(output: string) {
const parsed: PackageResult[] = [];
const lines = output.split("\n");
for (let index = 0; index < lines.length; index += 1) {
const header = lines[index];
const match = header.match(/^([^/\s]+)\/([^\s]+)\s+([^\s]+)(?:\s+\[(installed[^\]]*)\])?/);
if (!match) {
continue;
}
const descriptionLines: string[] = [];
let next = index + 1;
while (next < lines.length && /^\s+/.test(lines[next])) {
descriptionLines.push(lines[next].trim());
next += 1;
}
parsed.push({
repo: match[1],
name: match[2],
version: match[3],
installed: Boolean(match[4]),
description: descriptionLines.join(" ") || "Keine Beschreibung.",
});
index = next - 1;
}
return parsed.slice(0, 80);
}
function searchPackages() {
const term = query.trim();
if (!term) {
results = [];
statusMessage = "Suchbegriff fehlt.";
rebuild();
return;
}
setBusy(true);
errorMessage = "";
statusMessage = `Suche mit ${helper} nach "${term}"...`;
helperAvailable(helper)
.then(() => runShell(`${helper} -Ss ${shQuote(term)} 2>/dev/null || true`))
.then(output => {
results = parseSearch(output);
statusMessage = results.length
? `${results.length} Treffer fuer "${term}" mit ${helper}.`
: `Keine Treffer fuer "${term}".`;
})
.catch(error => {
console.error(error);
results = [];
errorMessage = `${helper} ist nicht verfuegbar oder die Suche ist fehlgeschlagen.`;
statusMessage = "Suche fehlgeschlagen.";
})
.finally(() => {
setBusy(false);
});
}
function terminalCommand(command: string) {
const hold = `${command}; printf '\\n'; read -r -p 'Enter zum Schliessen... ' _`;
return `kitty --title ${shQuote("Paketmanager")} sh -lc ${shQuote(hold)} || alacritty -e sh -lc ${shQuote(hold)} || foot sh -lc ${shQuote(hold)} || xterm -e sh -lc ${shQuote(hold)}`;
}
function installPackage(pkg: PackageResult) {
const command = helper === "pacman"
? `sudo pacman -S ${shQuote(pkg.name)}`
: `paru -S ${shQuote(pkg.name)}`;
notify(`Installation gestartet: ${pkg.name}`);
runShell(terminalCommand(command)).catch(error => {
console.error(error);
notify("Kein Terminal fuer Paketinstallation gefunden.");
});
}
function updateSystem() {
const command = helper === "pacman"
? "sudo pacman -Syu"
: "paru -Syu";
notify(`Update gestartet mit ${helper}.`);
runShell(terminalCommand(command)).catch(error => {
console.error(error);
notify("Kein Terminal fuer Updates gefunden.");
});
}
function HelperButton({ id, label }: { id: Helper; label: string }) {
return (
<button
class={helper === id ? "tool-button active" : "tool-button"}
onClicked={() => {
helper = id;
statusMessage = `Aktiver Helper: ${helper}`;
rebuild();
}}
label={label}
/>
);
}
function PackageRow({ pkg }: { pkg: PackageResult }) {
return (
<box class="package-row" spacing={10}>
<box orientation={Gtk.Orientation.VERTICAL} spacing={4} hexpand>
<box spacing={8}>
<label class="package-name" xalign={0} label={pkg.name} />
<label class="repo-pill" label={pkg.repo} />
{pkg.installed ? <label class="installed-pill" label="installed" /> : <box />}
</box>
<label class="package-meta" xalign={0} label={pkg.version} />
<label class="package-desc" xalign={0} wrap label={pkg.description} />
</box>
<button class="button primary" sensitive={!busy} onClicked={() => installPackage(pkg)} label="Installieren" />
</box>
);
}
function PackageManagerWindow() {
return (
<window
name="package-manager"
namespace="package-manager"
class="package-window"
visible
keymode={Astal.Keymode.EXCLUSIVE}
anchor={Astal.WindowAnchor.TOP}
application={app}
>
<Gtk.EventControllerKey onKeyPressed={(_, keyval) => {
if (keyval === ESC_KEYVAL) {
app.quit();
return true;
}
return false;
}} />
<box class="package-panel" orientation={Gtk.Orientation.VERTICAL} spacing={14} marginTop={WINDOW_MARGIN_TOP}>
<box class="header">
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
<label class="title" xalign={0} label="Paket Installation / Updates" />
<label class="subtitle" xalign={0} label="Pacman und AUR Suche, Installation im Terminal." />
</box>
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
<label label="" />
</button>
</box>
<box class="toolbar" spacing={8}>
<entry
hexpand
placeholderText="Paket suchen, z.B. firefox, docker, obsidian..."
onChanged={entry => {
query = entry.get_text();
}}
onActivate={entry => {
query = entry.get_text();
searchPackages();
}}
/>
<button class="button primary" sensitive={!busy} onClicked={searchPackages} label={busy ? "Suche..." : "Suchen"} />
</box>
<box class="toolbar" spacing={8}>
<HelperButton id="paru" label="paru" />
<HelperButton id="pacman" label="pacman" />
<button class="button" sensitive={!busy} onClicked={updateSystem} label="System updaten" />
</box>
<box class="status-strip">
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
</box>
<scrolledwindow
class="results-scroll"
hexpand
vexpand
hscrollbarPolicy={Gtk.PolicyType.NEVER}
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
>
{results.length
? (
<box orientation={Gtk.Orientation.VERTICAL} spacing={8}>
{results.map(pkg => <PackageRow pkg={pkg} />)}
</box>
)
: (
<box class="empty">
<label hexpand xalign={0.5} label="Noch keine Suchergebnisse." />
</box>
)}
</scrolledwindow>
</box>
</window>
);
}
function rebuild() {
const win = app.get_window("package-manager");
if (!win) {
return;
}
win.set_child(
<box class="package-panel" orientation={Gtk.Orientation.VERTICAL} spacing={14} marginTop={WINDOW_MARGIN_TOP}>
<box class="header">
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
<label class="title" xalign={0} label="Paket Installation / Updates" />
<label class="subtitle" xalign={0} label="Pacman und AUR Suche, Installation im Terminal." />
</box>
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
<label label="" />
</button>
</box>
<box class="toolbar" spacing={8}>
<entry
hexpand
text={query}
placeholderText="Paket suchen, z.B. firefox, docker, obsidian..."
onChanged={entry => {
query = entry.get_text();
}}
onActivate={entry => {
query = entry.get_text();
searchPackages();
}}
/>
<button class="button primary" sensitive={!busy} onClicked={searchPackages} label={busy ? "Suche..." : "Suchen"} />
</box>
<box class="toolbar" spacing={8}>
<HelperButton id="paru" label="paru" />
<HelperButton id="pacman" label="pacman" />
<button class="button" sensitive={!busy} onClicked={updateSystem} label="System updaten" />
</box>
<box class="status-strip">
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
</box>
<scrolledwindow
class="results-scroll"
hexpand
vexpand
hscrollbarPolicy={Gtk.PolicyType.NEVER}
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
>
{results.length
? (
<box orientation={Gtk.Orientation.VERTICAL} spacing={8}>
{results.map(pkg => <PackageRow pkg={pkg} />)}
</box>
)
: (
<box class="empty">
<label hexpand xalign={0.5} label="Noch keine Suchergebnisse." />
</box>
)}
</scrolledwindow>
</box> as Gtk.Widget
);
}
app.start({
css,
instanceName: "package-manager",
main() {
PackageManagerWindow();
},
});