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' \
|
printf '%s\n' \
|
||||||
" Hyprland neu laden" \
|
" Hyprland neu laden" \
|
||||||
" Waybar neu starten" \
|
" Waybar neu starten" \
|
||||||
|
" Paket Installation / Updates" \
|
||||||
" Bildschirm heller" \
|
" Bildschirm heller" \
|
||||||
" Bildschirm dunkler" \
|
" Bildschirm dunkler" \
|
||||||
" Session beenden" |
|
" Session beenden" |
|
||||||
@@ -53,6 +54,9 @@ case "$choice" in
|
|||||||
*"Waybar neu starten"*)
|
*"Waybar neu starten"*)
|
||||||
restart_waybar
|
restart_waybar
|
||||||
;;
|
;;
|
||||||
|
*"Paket Installation / Updates"*)
|
||||||
|
"$SCRIPT_DIR/package-manager.sh"
|
||||||
|
;;
|
||||||
*"Bildschirm heller"*)
|
*"Bildschirm heller"*)
|
||||||
if command -v brightnessctl >/dev/null 2>&1; then
|
if command -v brightnessctl >/dev/null 2>&1; then
|
||||||
brightnessctl -e4 -n2 set 5%+
|
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