Sync current Hyprland widgets
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import app from "ags/gtk4/app";
|
||||
import { Astal, Gtk } from "ags/gtk4";
|
||||
import { execAsync } from "ags/process";
|
||||
import { execAsync, subprocess, Process } from "ags/process";
|
||||
import GLib from "gi://GLib";
|
||||
import css from "./package-manager.css";
|
||||
|
||||
const WINDOW_MARGIN_TOP = 48;
|
||||
const ESC_KEYVAL = 65307;
|
||||
const RUNNER = `${GLib.get_home_dir()}/.config/hypr/Scripts/ags-package-runner.py`;
|
||||
|
||||
type Helper = "pacman" | "paru";
|
||||
|
||||
@@ -22,6 +24,12 @@ let results: PackageResult[] = [];
|
||||
let busy = false;
|
||||
let statusMessage = "Suchbegriff eingeben und Enter druecken.";
|
||||
let errorMessage = "";
|
||||
let activeProcess: Process | null = null;
|
||||
let operationTitle = "";
|
||||
let operationOutput = "";
|
||||
let operationInput = "";
|
||||
let secretInput = false;
|
||||
let lastOutput = "";
|
||||
|
||||
function shQuote(value: string) {
|
||||
return `'${value.replace(/'/g, `'\\''`)}'`;
|
||||
@@ -40,6 +48,17 @@ function setBusy(value: boolean) {
|
||||
rebuild();
|
||||
}
|
||||
|
||||
function appendOperationOutput(data: string) {
|
||||
operationOutput = `${operationOutput}${data}`.slice(-30000);
|
||||
lastOutput = data;
|
||||
secretInput = /(\[sudo\].*password|passwort|password).*:/i.test(data);
|
||||
rebuild();
|
||||
}
|
||||
|
||||
function commandLabel(command: string[]) {
|
||||
return command.map(shQuote).join(" ");
|
||||
}
|
||||
|
||||
function helperAvailable(nextHelper: Helper) {
|
||||
return runShell(`command -v ${nextHelper} >/dev/null 2>&1`);
|
||||
}
|
||||
@@ -109,33 +128,111 @@ function searchPackages() {
|
||||
});
|
||||
}
|
||||
|
||||
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 startOperation(title: string, command: string[]) {
|
||||
if (activeProcess) {
|
||||
statusMessage = "Es laeuft bereits ein Paketprozess.";
|
||||
rebuild();
|
||||
return;
|
||||
}
|
||||
|
||||
operationTitle = title;
|
||||
operationOutput = `$ ${commandLabel(command)}\n\n`;
|
||||
operationInput = "";
|
||||
errorMessage = "";
|
||||
statusMessage = `${title} laeuft in AGS.`;
|
||||
busy = true;
|
||||
|
||||
const process = subprocess(
|
||||
[RUNNER, "--", ...command],
|
||||
line => {
|
||||
try {
|
||||
const event = JSON.parse(line);
|
||||
if (event.type === "out") {
|
||||
appendOperationOutput(event.data || "");
|
||||
} else if (event.type === "exit") {
|
||||
const suffix = event.signaled ? `Signal ${event.code}` : `Exit ${event.code}`;
|
||||
appendOperationOutput(`\n[${suffix}]\n`);
|
||||
}
|
||||
} catch {
|
||||
appendOperationOutput(`${line}\n`);
|
||||
}
|
||||
},
|
||||
line => appendOperationOutput(`${line}\n`),
|
||||
);
|
||||
|
||||
activeProcess = process;
|
||||
process.connect("exit", (_, code: number, signaled: boolean) => {
|
||||
activeProcess = null;
|
||||
busy = false;
|
||||
secretInput = false;
|
||||
statusMessage = signaled
|
||||
? `${title} wurde beendet.`
|
||||
: code === 0
|
||||
? `${title} abgeschlossen.`
|
||||
: `${title} fehlgeschlagen (Exit ${code}).`;
|
||||
notify(statusMessage);
|
||||
rebuild();
|
||||
});
|
||||
|
||||
rebuild();
|
||||
}
|
||||
|
||||
function sendOperationInput(value = operationInput, allowEmpty = false) {
|
||||
if (!activeProcess || (!allowEmpty && value.length === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeProcess.writeAsync(`${JSON.stringify({ type: "input", data: `${value}\n` })}\n`).catch(error => {
|
||||
console.error(error);
|
||||
errorMessage = "Eingabe konnte nicht gesendet werden.";
|
||||
rebuild();
|
||||
});
|
||||
operationInput = "";
|
||||
secretInput = false;
|
||||
rebuild();
|
||||
}
|
||||
|
||||
function yesAnswer() {
|
||||
return /[\[(][YyJj]\/[Nn][\])]/.test(lastOutput) ? "" : "y";
|
||||
}
|
||||
|
||||
function sendYesAnswer() {
|
||||
const answer = yesAnswer();
|
||||
appendOperationOutput(answer ? `\n> ${answer}\n` : "\n> Enter\n");
|
||||
sendOperationInput(answer, true);
|
||||
}
|
||||
|
||||
function sendNoAnswer() {
|
||||
appendOperationOutput("\n> n\n");
|
||||
sendOperationInput("n");
|
||||
}
|
||||
|
||||
function cancelOperation() {
|
||||
if (!activeProcess) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeProcess.writeAsync(`${JSON.stringify({ type: "signal", signal: 15 })}\n`).catch(console.error);
|
||||
statusMessage = "Abbruch angefordert.";
|
||||
rebuild();
|
||||
}
|
||||
|
||||
function installPackage(pkg: PackageResult) {
|
||||
const command = helper === "pacman"
|
||||
? `sudo pacman -S ${shQuote(pkg.name)}`
|
||||
: `paru -S ${shQuote(pkg.name)}`;
|
||||
? ["sudo", "pacman", "-S", pkg.name]
|
||||
: ["paru", "-S", pkg.name];
|
||||
|
||||
notify(`Installation gestartet: ${pkg.name}`);
|
||||
runShell(terminalCommand(command)).catch(error => {
|
||||
console.error(error);
|
||||
notify("Kein Terminal fuer Paketinstallation gefunden.");
|
||||
});
|
||||
startOperation(`Installation: ${pkg.name}`, command);
|
||||
}
|
||||
|
||||
function updateSystem() {
|
||||
const command = helper === "pacman"
|
||||
? "sudo pacman -Syu"
|
||||
: "paru -Syu";
|
||||
? ["sudo", "pacman", "-Syu"]
|
||||
: ["paru", "-Syu"];
|
||||
|
||||
notify(`Update gestartet mit ${helper}.`);
|
||||
runShell(terminalCommand(command)).catch(error => {
|
||||
console.error(error);
|
||||
notify("Kein Terminal fuer Updates gefunden.");
|
||||
});
|
||||
startOperation(`Systemupdate mit ${helper}`, command);
|
||||
}
|
||||
|
||||
function HelperButton({ id, label }: { id: Helper; label: string }) {
|
||||
@@ -169,6 +266,49 @@ function PackageRow({ pkg }: { pkg: PackageResult }) {
|
||||
);
|
||||
}
|
||||
|
||||
function OperationPanel() {
|
||||
if (!operationTitle && !operationOutput) {
|
||||
return <box />;
|
||||
}
|
||||
|
||||
return (
|
||||
<box class="operation-panel" orientation={Gtk.Orientation.VERTICAL} spacing={8}>
|
||||
<box class="operation-header" spacing={8}>
|
||||
<label class="operation-title" xalign={0} hexpand label={operationTitle || "Paketprozess"} />
|
||||
<button class="button danger" sensitive={Boolean(activeProcess)} onClicked={cancelOperation} label="Abbrechen" />
|
||||
</box>
|
||||
<scrolledwindow
|
||||
class="operation-scroll"
|
||||
hexpand
|
||||
vexpand
|
||||
hscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||
>
|
||||
<label class="operation-output" xalign={0} yalign={0} wrap selectable label={operationOutput || "Warte auf Ausgabe..."} />
|
||||
</scrolledwindow>
|
||||
<box class="operation-input-row" spacing={8}>
|
||||
<entry
|
||||
hexpand
|
||||
sensitive={Boolean(activeProcess)}
|
||||
visibility={!secretInput}
|
||||
text={operationInput}
|
||||
placeholderText={secretInput ? "Passwort eingeben..." : "Antwort eingeben..."}
|
||||
onChanged={entry => {
|
||||
operationInput = entry.get_text();
|
||||
}}
|
||||
onActivate={entry => {
|
||||
operationInput = entry.get_text();
|
||||
sendOperationInput();
|
||||
}}
|
||||
/>
|
||||
<button class="button" sensitive={Boolean(activeProcess)} onClicked={sendYesAnswer} label="Ja" />
|
||||
<button class="button" sensitive={Boolean(activeProcess)} onClicked={sendNoAnswer} label="Nein" />
|
||||
<button class="button primary" sensitive={Boolean(activeProcess)} onClicked={() => sendOperationInput()} label="Senden" />
|
||||
</box>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
|
||||
function PackageManagerWindow() {
|
||||
return (
|
||||
<window
|
||||
@@ -191,7 +331,7 @@ function PackageManagerWindow() {
|
||||
<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." />
|
||||
<label class="subtitle" xalign={0} label="Pacman und AUR Suche, Installation direkt in AGS." />
|
||||
</box>
|
||||
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
|
||||
<label label="" />
|
||||
@@ -219,6 +359,7 @@ function PackageManagerWindow() {
|
||||
<box class="status-strip">
|
||||
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
|
||||
</box>
|
||||
<OperationPanel />
|
||||
<scrolledwindow
|
||||
class="results-scroll"
|
||||
hexpand
|
||||
@@ -253,7 +394,7 @@ function rebuild() {
|
||||
<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." />
|
||||
<label class="subtitle" xalign={0} label="Pacman und AUR Suche, Installation direkt in AGS." />
|
||||
</box>
|
||||
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
|
||||
<label label="" />
|
||||
@@ -282,6 +423,7 @@ function rebuild() {
|
||||
<box class="status-strip">
|
||||
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
|
||||
</box>
|
||||
<OperationPanel />
|
||||
<scrolledwindow
|
||||
class="results-scroll"
|
||||
hexpand
|
||||
|
||||
Reference in New Issue
Block a user