Add AGS package manager
This commit is contained in:
314
config/hypr/ags/package-manager.tsx
Normal file
314
config/hypr/ags/package-manager.tsx
Normal 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();
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user