Add AGS package manager
This commit is contained in:
18
config/hypr/Scripts/package-manager.sh
Executable file
18
config/hypr/Scripts/package-manager.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
notify() {
|
||||
notify-send "Pakete" "$1" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
if ! command -v ags >/dev/null 2>&1; then
|
||||
notify "ags ist nicht installiert."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$HYPR_DIR"
|
||||
ags quit --instance package-manager >/dev/null 2>&1 || true
|
||||
exec ags run "$HYPR_DIR/ags/package-manager.tsx"
|
||||
@@ -40,6 +40,7 @@ choice="$(
|
||||
printf '%s\n' \
|
||||
" Hyprland neu laden" \
|
||||
" Waybar neu starten" \
|
||||
" Paket Installation / Updates" \
|
||||
" Bildschirm heller" \
|
||||
" Bildschirm dunkler" \
|
||||
" Session beenden" |
|
||||
@@ -53,6 +54,9 @@ case "$choice" in
|
||||
*"Waybar neu starten"*)
|
||||
restart_waybar
|
||||
;;
|
||||
*"Paket Installation / Updates"*)
|
||||
"$SCRIPT_DIR/package-manager.sh"
|
||||
;;
|
||||
*"Bildschirm heller"*)
|
||||
if command -v brightnessctl >/dev/null 2>&1; then
|
||||
brightnessctl -e4 -n2 set 5%+
|
||||
|
||||
144
config/hypr/ags/package-manager.css
Normal file
144
config/hypr/ags/package-manager.css
Normal file
@@ -0,0 +1,144 @@
|
||||
* {
|
||||
all: unset;
|
||||
font-family: "JetBrainsMono Nerd Font", "Noto Sans", sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.package-window {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.package-panel {
|
||||
min-width: 940px;
|
||||
min-height: 620px;
|
||||
border: 1px solid rgba(205, 214, 244, 0.16);
|
||||
border-radius: 16px;
|
||||
background: rgba(20, 20, 30, 0.97);
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
|
||||
color: #cdd6f4;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.header {
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #00ff9c;
|
||||
font-size: 21px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.subtitle,
|
||||
.muted,
|
||||
.package-meta {
|
||||
color: #a6adc8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
entry {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(205, 214, 244, 0.14);
|
||||
border-radius: 10px;
|
||||
background: rgba(40, 40, 55, 0.82);
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
entry:focus {
|
||||
border-color: rgba(0, 255, 156, 0.78);
|
||||
}
|
||||
|
||||
.button,
|
||||
.tool-button,
|
||||
.icon-button {
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(40, 40, 55, 0.82);
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus,
|
||||
.tool-button:hover,
|
||||
.tool-button:focus,
|
||||
.icon-button:hover,
|
||||
.icon-button:focus {
|
||||
background: rgba(0, 255, 156, 0.22);
|
||||
}
|
||||
|
||||
.primary,
|
||||
.tool-button.active {
|
||||
background: rgba(0, 255, 156, 0.24);
|
||||
color: #00ff9c;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: #f38ba8;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
min-width: 34px;
|
||||
min-height: 34px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.status-strip {
|
||||
min-height: 30px;
|
||||
padding: 7px 10px;
|
||||
border: 1px solid rgba(205, 214, 244, 0.10);
|
||||
border-radius: 8px;
|
||||
background: rgba(40, 40, 55, 0.48);
|
||||
}
|
||||
|
||||
.results-scroll {
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.package-row {
|
||||
min-height: 76px;
|
||||
padding: 10px;
|
||||
border: 1px solid rgba(205, 214, 244, 0.10);
|
||||
border-radius: 8px;
|
||||
background: rgba(40, 40, 55, 0.62);
|
||||
}
|
||||
|
||||
.package-row:hover {
|
||||
border-color: rgba(0, 255, 156, 0.35);
|
||||
background: rgba(40, 40, 55, 0.82);
|
||||
}
|
||||
|
||||
.package-name {
|
||||
color: #cdd6f4;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.package-desc {
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
.repo-pill,
|
||||
.installed-pill {
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
background: rgba(116, 199, 236, 0.16);
|
||||
color: #89dceb;
|
||||
}
|
||||
|
||||
.installed-pill {
|
||||
background: rgba(0, 255, 156, 0.16);
|
||||
color: #00ff9c;
|
||||
}
|
||||
|
||||
.empty {
|
||||
min-height: 180px;
|
||||
color: #a6adc8;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f38ba8;
|
||||
}
|
||||
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