Sync current Hyprland widgets
This commit is contained in:
91
config/hypr/Scripts/ags-package-runner.py
Executable file
91
config/hypr/Scripts/ags-package-runner.py
Executable file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pty
|
||||||
|
import select
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def emit(event_type, **payload):
|
||||||
|
print(json.dumps({"type": event_type, **payload}), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_message(pid, fd, raw_line):
|
||||||
|
try:
|
||||||
|
message = json.loads(raw_line.decode("utf-8", "replace"))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return
|
||||||
|
|
||||||
|
if message.get("type") == "input":
|
||||||
|
os.write(fd, str(message.get("data", "")).encode("utf-8", "replace"))
|
||||||
|
elif message.get("type") == "signal":
|
||||||
|
os.kill(pid, int(message.get("signal", 15)))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if "--" not in sys.argv:
|
||||||
|
emit("exit", code=2, signaled=False)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
command = sys.argv[sys.argv.index("--") + 1 :]
|
||||||
|
if not command:
|
||||||
|
emit("exit", code=2, signaled=False)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
pid, fd = pty.fork()
|
||||||
|
if pid == 0:
|
||||||
|
os.execvp(command[0], command)
|
||||||
|
|
||||||
|
emit("start", pid=pid)
|
||||||
|
stdin_fd = sys.stdin.fileno()
|
||||||
|
open_fds = [fd, stdin_fd]
|
||||||
|
stdin_buffer = b""
|
||||||
|
|
||||||
|
while open_fds:
|
||||||
|
readable, _, _ = select.select(open_fds, [], [], 0.2)
|
||||||
|
|
||||||
|
if fd in readable:
|
||||||
|
try:
|
||||||
|
data = os.read(fd, 4096)
|
||||||
|
except OSError:
|
||||||
|
data = b""
|
||||||
|
|
||||||
|
if data:
|
||||||
|
emit("out", data=data.decode("utf-8", "replace"))
|
||||||
|
else:
|
||||||
|
open_fds.remove(fd)
|
||||||
|
|
||||||
|
if stdin_fd in readable:
|
||||||
|
try:
|
||||||
|
chunk = os.read(stdin_fd, 4096)
|
||||||
|
except OSError:
|
||||||
|
chunk = b""
|
||||||
|
|
||||||
|
if not chunk:
|
||||||
|
open_fds.remove(stdin_fd)
|
||||||
|
continue
|
||||||
|
|
||||||
|
stdin_buffer += chunk
|
||||||
|
while b"\n" in stdin_buffer:
|
||||||
|
line, stdin_buffer = stdin_buffer.split(b"\n", 1)
|
||||||
|
handle_message(pid, fd, line)
|
||||||
|
|
||||||
|
try:
|
||||||
|
finished_pid, status = os.waitpid(pid, os.WNOHANG)
|
||||||
|
except ChildProcessError:
|
||||||
|
break
|
||||||
|
|
||||||
|
if finished_pid == pid:
|
||||||
|
if os.WIFSIGNALED(status):
|
||||||
|
emit("exit", code=os.WTERMSIG(status), signaled=True)
|
||||||
|
return 128 + os.WTERMSIG(status)
|
||||||
|
|
||||||
|
code = os.WEXITSTATUS(status)
|
||||||
|
emit("exit", code=code, signaled=False)
|
||||||
|
return code
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -322,11 +322,41 @@ docker_menu() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
launch_codex() {
|
||||||
|
if ! command -v kitty >/dev/null 2>&1; then
|
||||||
|
notify "kitty ist nicht installiert."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v codex >/dev/null 2>&1; then
|
||||||
|
notify "codex ist nicht installiert."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
kitty --title "Codex" sh -lc 'cd "$HOME" && exec codex' >/dev/null 2>&1 &
|
||||||
|
}
|
||||||
|
|
||||||
|
launch_opencode() {
|
||||||
|
if ! command -v kitty >/dev/null 2>&1; then
|
||||||
|
notify "kitty ist nicht installiert."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v opencode >/dev/null 2>&1; then
|
||||||
|
notify "opencode ist nicht installiert."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
kitty --title "opencode" sh -lc 'cd "$HOME" && exec opencode' >/dev/null 2>&1 &
|
||||||
|
}
|
||||||
|
|
||||||
choice="$(
|
choice="$(
|
||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
"📁 Projekt Management" \
|
"📁 Projekt Management" \
|
||||||
" Homelab Controlcenter" \
|
" Homelab Controlcenter" \
|
||||||
"🐳 Docker Control" \
|
"🐳 Docker Control" \
|
||||||
|
" Codex" \
|
||||||
|
" opencode" \
|
||||||
" Terminal" \
|
" Terminal" \
|
||||||
" Projektordner" \
|
" Projektordner" \
|
||||||
" VS Code / Codium" \
|
" VS Code / Codium" \
|
||||||
@@ -344,6 +374,12 @@ case "$choice" in
|
|||||||
*"Docker Control"*)
|
*"Docker Control"*)
|
||||||
docker_menu
|
docker_menu
|
||||||
;;
|
;;
|
||||||
|
*"Codex"*)
|
||||||
|
launch_codex
|
||||||
|
;;
|
||||||
|
*"opencode"*)
|
||||||
|
launch_opencode
|
||||||
|
;;
|
||||||
*"Terminal"*)
|
*"Terminal"*)
|
||||||
kitty
|
kitty
|
||||||
;;
|
;;
|
||||||
|
|||||||
26
config/hypr/Scripts/widget-panel.sh
Executable file
26
config/hypr/Scripts/widget-panel.sh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
export HYPR_DIR
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send "Widgetbereich" "$1" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! command -v ags >/dev/null 2>&1; then
|
||||||
|
notify "ags ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t AGS_INSTANCES < <(ags list 2>/dev/null || true)
|
||||||
|
for INSTANCE in "${AGS_INSTANCES[@]}"; do
|
||||||
|
if [[ "$INSTANCE" == "widget-panel" ]]; then
|
||||||
|
ags toggle widget-panel --instance widget-panel >/dev/null 2>&1 || true
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$HYPR_DIR"
|
||||||
|
nohup ags run "$HYPR_DIR/ags/widget-panel.tsx" >/dev/null 2>&1 &
|
||||||
@@ -94,6 +94,42 @@ entry:focus {
|
|||||||
background: rgba(40, 40, 55, 0.48);
|
background: rgba(40, 40, 55, 0.48);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.operation-panel {
|
||||||
|
min-height: 220px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(10, 10, 16, 0.58);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-header,
|
||||||
|
.operation-input-row {
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-title {
|
||||||
|
color: #00ff9c;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-scroll {
|
||||||
|
min-height: 142px;
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.10);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(4, 4, 8, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-output {
|
||||||
|
padding: 10px;
|
||||||
|
color: #cdd6f4;
|
||||||
|
font-family: "JetBrainsMono Nerd Font", monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
color: #f38ba8;
|
||||||
|
}
|
||||||
|
|
||||||
.results-scroll {
|
.results-scroll {
|
||||||
min-height: 420px;
|
min-height: 420px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import app from "ags/gtk4/app";
|
import app from "ags/gtk4/app";
|
||||||
import { Astal, Gtk } from "ags/gtk4";
|
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";
|
import css from "./package-manager.css";
|
||||||
|
|
||||||
const WINDOW_MARGIN_TOP = 48;
|
const WINDOW_MARGIN_TOP = 48;
|
||||||
const ESC_KEYVAL = 65307;
|
const ESC_KEYVAL = 65307;
|
||||||
|
const RUNNER = `${GLib.get_home_dir()}/.config/hypr/Scripts/ags-package-runner.py`;
|
||||||
|
|
||||||
type Helper = "pacman" | "paru";
|
type Helper = "pacman" | "paru";
|
||||||
|
|
||||||
@@ -22,6 +24,12 @@ let results: PackageResult[] = [];
|
|||||||
let busy = false;
|
let busy = false;
|
||||||
let statusMessage = "Suchbegriff eingeben und Enter druecken.";
|
let statusMessage = "Suchbegriff eingeben und Enter druecken.";
|
||||||
let errorMessage = "";
|
let errorMessage = "";
|
||||||
|
let activeProcess: Process | null = null;
|
||||||
|
let operationTitle = "";
|
||||||
|
let operationOutput = "";
|
||||||
|
let operationInput = "";
|
||||||
|
let secretInput = false;
|
||||||
|
let lastOutput = "";
|
||||||
|
|
||||||
function shQuote(value: string) {
|
function shQuote(value: string) {
|
||||||
return `'${value.replace(/'/g, `'\\''`)}'`;
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
||||||
@@ -40,6 +48,17 @@ function setBusy(value: boolean) {
|
|||||||
rebuild();
|
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) {
|
function helperAvailable(nextHelper: Helper) {
|
||||||
return runShell(`command -v ${nextHelper} >/dev/null 2>&1`);
|
return runShell(`command -v ${nextHelper} >/dev/null 2>&1`);
|
||||||
}
|
}
|
||||||
@@ -109,33 +128,111 @@ function searchPackages() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function terminalCommand(command: string) {
|
function startOperation(title: string, command: string[]) {
|
||||||
const hold = `${command}; printf '\\n'; read -r -p 'Enter zum Schliessen... ' _`;
|
if (activeProcess) {
|
||||||
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)}`;
|
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) {
|
function installPackage(pkg: PackageResult) {
|
||||||
const command = helper === "pacman"
|
const command = helper === "pacman"
|
||||||
? `sudo pacman -S ${shQuote(pkg.name)}`
|
? ["sudo", "pacman", "-S", pkg.name]
|
||||||
: `paru -S ${shQuote(pkg.name)}`;
|
: ["paru", "-S", pkg.name];
|
||||||
|
|
||||||
notify(`Installation gestartet: ${pkg.name}`);
|
notify(`Installation gestartet: ${pkg.name}`);
|
||||||
runShell(terminalCommand(command)).catch(error => {
|
startOperation(`Installation: ${pkg.name}`, command);
|
||||||
console.error(error);
|
|
||||||
notify("Kein Terminal fuer Paketinstallation gefunden.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSystem() {
|
function updateSystem() {
|
||||||
const command = helper === "pacman"
|
const command = helper === "pacman"
|
||||||
? "sudo pacman -Syu"
|
? ["sudo", "pacman", "-Syu"]
|
||||||
: "paru -Syu";
|
: ["paru", "-Syu"];
|
||||||
|
|
||||||
notify(`Update gestartet mit ${helper}.`);
|
notify(`Update gestartet mit ${helper}.`);
|
||||||
runShell(terminalCommand(command)).catch(error => {
|
startOperation(`Systemupdate mit ${helper}`, command);
|
||||||
console.error(error);
|
|
||||||
notify("Kein Terminal fuer Updates gefunden.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function HelperButton({ id, label }: { id: Helper; label: string }) {
|
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() {
|
function PackageManagerWindow() {
|
||||||
return (
|
return (
|
||||||
<window
|
<window
|
||||||
@@ -191,7 +331,7 @@ function PackageManagerWindow() {
|
|||||||
<box class="header">
|
<box class="header">
|
||||||
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
|
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
|
||||||
<label class="title" xalign={0} label="Paket Installation / Updates" />
|
<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>
|
</box>
|
||||||
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
|
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
|
||||||
<label label="" />
|
<label label="" />
|
||||||
@@ -219,6 +359,7 @@ function PackageManagerWindow() {
|
|||||||
<box class="status-strip">
|
<box class="status-strip">
|
||||||
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
|
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
|
||||||
</box>
|
</box>
|
||||||
|
<OperationPanel />
|
||||||
<scrolledwindow
|
<scrolledwindow
|
||||||
class="results-scroll"
|
class="results-scroll"
|
||||||
hexpand
|
hexpand
|
||||||
@@ -253,7 +394,7 @@ function rebuild() {
|
|||||||
<box class="header">
|
<box class="header">
|
||||||
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
|
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
|
||||||
<label class="title" xalign={0} label="Paket Installation / Updates" />
|
<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>
|
</box>
|
||||||
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
|
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
|
||||||
<label label="" />
|
<label label="" />
|
||||||
@@ -282,6 +423,7 @@ function rebuild() {
|
|||||||
<box class="status-strip">
|
<box class="status-strip">
|
||||||
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
|
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
|
||||||
</box>
|
</box>
|
||||||
|
<OperationPanel />
|
||||||
<scrolledwindow
|
<scrolledwindow
|
||||||
class="results-scroll"
|
class="results-scroll"
|
||||||
hexpand
|
hexpand
|
||||||
|
|||||||
138
config/hypr/ags/widget-panel.css
Normal file
138
config/hypr/ags/widget-panel.css
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
font-family: "JetBrainsMono Nerd Font", "Noto Sans", sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-window {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-scroll {
|
||||||
|
border: 1px solid alpha(@ags_fg, 0.16);
|
||||||
|
border-radius: 16px;
|
||||||
|
background: @ags_bg;
|
||||||
|
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.48);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
padding: 16px;
|
||||||
|
color: @ags_fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle,
|
||||||
|
.card-subtitle {
|
||||||
|
color: @ags_muted;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
min-width: 36px;
|
||||||
|
min-height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: @ags_fg;
|
||||||
|
background: alpha(@ags_panel, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:hover,
|
||||||
|
.icon-button:focus {
|
||||||
|
background: alpha(@ags_accent, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: @ags_accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid alpha(@ags_fg, 0.10);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: alpha(@ags_panel, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric {
|
||||||
|
min-height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-name {
|
||||||
|
color: @ags_fg;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
color: @ags_accent;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
min-height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: alpha(@ags_bg_soft, 0.70);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
min-height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: @ags_accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-row {
|
||||||
|
color: @ags_muted;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sparkline {
|
||||||
|
color: @ags_accent_2;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grid {
|
||||||
|
min-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday,
|
||||||
|
.day {
|
||||||
|
min-width: 34px;
|
||||||
|
min-height: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday {
|
||||||
|
color: @ags_muted;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
background: alpha(@ags_bg_soft, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.day.today {
|
||||||
|
color: @ags_bg;
|
||||||
|
background: @ags_accent;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day.muted {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-main {
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: @ags_accent_2;
|
||||||
|
}
|
||||||
435
config/hypr/ags/widget-panel.tsx
Normal file
435
config/hypr/ags/widget-panel.tsx
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
import app from "ags/gtk4/app";
|
||||||
|
import { Astal, Gtk } from "ags/gtk4";
|
||||||
|
import { readFile } from "ags/file";
|
||||||
|
import { execAsync } from "ags/process";
|
||||||
|
import { createRoot } from "gnim";
|
||||||
|
import css from "./widget-panel.css";
|
||||||
|
import GLib from "gi://GLib";
|
||||||
|
|
||||||
|
const HYPR_DIR = GLib.getenv("HYPR_DIR") || `${GLib.get_home_dir()}/.config/hypr`;
|
||||||
|
const THEME_DIR = `${HYPR_DIR}/Themes`;
|
||||||
|
const CURRENT_WALLPAPER = `${HYPR_DIR}/current-wallpaper`;
|
||||||
|
const REFRESH_SECONDS = 2;
|
||||||
|
const WEATHER_SECONDS = 20 * 60;
|
||||||
|
const HISTORY_LIMIT = 22;
|
||||||
|
const ESC_KEYVAL = 65307;
|
||||||
|
const START_HIDDEN = GLib.getenv("WIDGET_PANEL_START_HIDDEN") === "1";
|
||||||
|
|
||||||
|
type UiTheme = {
|
||||||
|
accent: string;
|
||||||
|
accent2: string;
|
||||||
|
background: string;
|
||||||
|
backgroundSoft: string;
|
||||||
|
foreground: string;
|
||||||
|
muted: string;
|
||||||
|
panelHex: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SystemSnapshot = {
|
||||||
|
cpu: number;
|
||||||
|
memory: number;
|
||||||
|
disk: number;
|
||||||
|
temp: string;
|
||||||
|
uptime: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastCpuTotal = 0;
|
||||||
|
let lastCpuIdle = 0;
|
||||||
|
let system: SystemSnapshot = {
|
||||||
|
cpu: 0,
|
||||||
|
memory: 0,
|
||||||
|
disk: 0,
|
||||||
|
temp: "n/a",
|
||||||
|
uptime: "n/a",
|
||||||
|
};
|
||||||
|
let cpuHistory: number[] = [];
|
||||||
|
let weather = "Wetter wird geladen...";
|
||||||
|
let weatherUpdated = 0;
|
||||||
|
let timerStarted = false;
|
||||||
|
let disposeRebuild: (() => void) | null = null;
|
||||||
|
let panelWindow: Gtk.Window | null = null;
|
||||||
|
|
||||||
|
function readText(path: string) {
|
||||||
|
try {
|
||||||
|
return readFile(path);
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listFiles(dir: string, predicate: (path: string, name: string) => boolean) {
|
||||||
|
try {
|
||||||
|
const directory = GLib.Dir.open(dir, 0);
|
||||||
|
const files: string[] = [];
|
||||||
|
let name = directory.read_name();
|
||||||
|
|
||||||
|
while (name !== null) {
|
||||||
|
const path = `${dir}/${name}`;
|
||||||
|
if (predicate(path, name)) {
|
||||||
|
files.push(path);
|
||||||
|
}
|
||||||
|
name = directory.read_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.sort((a, b) => a.localeCompare(b));
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shellValue(contents: string, key: string) {
|
||||||
|
const regex = new RegExp(`^${key}=(["']?)(.*?)\\1$`, "m");
|
||||||
|
return contents.match(regex)?.[2] || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentWallpaper() {
|
||||||
|
return readText(CURRENT_WALLPAPER).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function activeTheme(): UiTheme {
|
||||||
|
const fallback = {
|
||||||
|
accent: "#f38ba8",
|
||||||
|
accent2: "#cba6f7",
|
||||||
|
background: "rgba(24, 20, 31, 0.96)",
|
||||||
|
backgroundSoft: "rgba(49, 50, 68, 0.82)",
|
||||||
|
foreground: "#f5e0dc",
|
||||||
|
muted: "#cdd6f4",
|
||||||
|
panelHex: "#313244",
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeWallpaper = currentWallpaper();
|
||||||
|
const themeFile = listFiles(THEME_DIR, (_path, name) => name.endsWith(".theme"))
|
||||||
|
.find(path => shellValue(readText(path), "WALLPAPER") === activeWallpaper);
|
||||||
|
|
||||||
|
if (!themeFile) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contents = readText(themeFile);
|
||||||
|
return {
|
||||||
|
accent: shellValue(contents, "ACCENT") || fallback.accent,
|
||||||
|
accent2: shellValue(contents, "ACCENT_2") || fallback.accent2,
|
||||||
|
background: shellValue(contents, "BACKGROUND") || fallback.background,
|
||||||
|
backgroundSoft: shellValue(contents, "BACKGROUND_SOFT") || fallback.backgroundSoft,
|
||||||
|
foreground: shellValue(contents, "FOREGROUND") || fallback.foreground,
|
||||||
|
muted: shellValue(contents, "MUTED") || fallback.muted,
|
||||||
|
panelHex: shellValue(contents, "PANEL_HEX") || fallback.panelHex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function themeCss(theme: UiTheme) {
|
||||||
|
return [
|
||||||
|
`@define-color ags_accent ${theme.accent};`,
|
||||||
|
`@define-color ags_accent_2 ${theme.accent2};`,
|
||||||
|
`@define-color ags_bg ${theme.background};`,
|
||||||
|
`@define-color ags_bg_soft ${theme.backgroundSoft};`,
|
||||||
|
`@define-color ags_fg ${theme.foreground};`,
|
||||||
|
`@define-color ags_muted ${theme.muted};`,
|
||||||
|
`@define-color ags_panel ${theme.panelHex};`,
|
||||||
|
css,
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(value: number, min = 0, max = 100) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function percentLabel(value: number) {
|
||||||
|
return `${Math.round(clamp(value))}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSystem() {
|
||||||
|
const cpuLine = readText("/proc/stat").split("\n")[0] || "";
|
||||||
|
const cpuValues = cpuLine.trim().split(/\s+/).slice(1).map(value => Number(value) || 0);
|
||||||
|
const idle = (cpuValues[3] || 0) + (cpuValues[4] || 0);
|
||||||
|
const total = cpuValues.reduce((sum, value) => sum + value, 0);
|
||||||
|
const totalDelta = total - lastCpuTotal;
|
||||||
|
const idleDelta = idle - lastCpuIdle;
|
||||||
|
|
||||||
|
if (lastCpuTotal > 0 && totalDelta > 0) {
|
||||||
|
system.cpu = clamp(((totalDelta - idleDelta) / totalDelta) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCpuTotal = total;
|
||||||
|
lastCpuIdle = idle;
|
||||||
|
|
||||||
|
const meminfo = readText("/proc/meminfo");
|
||||||
|
const memTotal = Number(meminfo.match(/^MemTotal:\s+(\d+)/m)?.[1] || 0);
|
||||||
|
const memAvailable = Number(meminfo.match(/^MemAvailable:\s+(\d+)/m)?.[1] || 0);
|
||||||
|
if (memTotal > 0) {
|
||||||
|
system.memory = clamp(((memTotal - memAvailable) / memTotal) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uptimeSeconds = Number(readText("/proc/uptime").split(" ")[0] || 0);
|
||||||
|
const hours = Math.floor(uptimeSeconds / 3600);
|
||||||
|
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
|
||||||
|
system.uptime = `${hours}h ${minutes}m`;
|
||||||
|
|
||||||
|
const temps = listFiles("/sys/class/thermal", (_path, name) => name.startsWith("thermal_zone"))
|
||||||
|
.map(path => Number(readText(`${path}/temp`).trim()) / 1000)
|
||||||
|
.filter(value => Number.isFinite(value) && value > 0);
|
||||||
|
system.temp = temps.length ? `${Math.round(Math.max(...temps))} C` : "n/a";
|
||||||
|
|
||||||
|
execAsync(["bash", "-lc", "df -P / | awk 'NR==2 {gsub(/%/, \"\", $5); print $5}'"])
|
||||||
|
.then(output => {
|
||||||
|
system.disk = clamp(Number(output.trim()) || system.disk);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
cpuHistory = [...cpuHistory, system.cpu].slice(-HISTORY_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWeather(force = false) {
|
||||||
|
if (!force && Date.now() - weatherUpdated < WEATHER_SECONDS * 1000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
weatherUpdated = Date.now();
|
||||||
|
execAsync(["bash", "-lc", "curl -fsS --max-time 5 'https://wttr.in/?format=%l:+%c+%t+%w' 2>/dev/null || true"])
|
||||||
|
.then(output => {
|
||||||
|
weather = output.trim() || "Wetter nicht erreichbar";
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
weather = "Wetter nicht erreichbar";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sparkline(values: number[]) {
|
||||||
|
const blocks = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
||||||
|
if (!values.length) {
|
||||||
|
return "·".repeat(HISTORY_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
.map(value => blocks[Math.round((clamp(value) / 100) * (blocks.length - 1))])
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function monthDays() {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = now.getMonth();
|
||||||
|
const first = new Date(year, month, 1);
|
||||||
|
const last = new Date(year, month + 1, 0);
|
||||||
|
const startOffset = (first.getDay() + 6) % 7;
|
||||||
|
const cells: { day: string; today: boolean; muted: boolean }[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < startOffset; i += 1) {
|
||||||
|
cells.push({ day: "", today: false, muted: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let day = 1; day <= last.getDate(); day += 1) {
|
||||||
|
cells.push({ day: String(day), today: day === now.getDate(), muted: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cells.length % 7 !== 0) {
|
||||||
|
cells.push({ day: "", today: false, muted: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
function monthTitle() {
|
||||||
|
return new Intl.DateTimeFormat("de-DE", { month: "long", year: "numeric" }).format(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
function Metric({ icon, label, value }: { icon: string; label: string; value: number }) {
|
||||||
|
return (
|
||||||
|
<box class="metric" orientation={Gtk.Orientation.VERTICAL} spacing={8}>
|
||||||
|
<box>
|
||||||
|
<label class="metric-name" hexpand xalign={0} label={`${icon} ${label}`} />
|
||||||
|
<label class="metric-value" label={percentLabel(value)} />
|
||||||
|
</box>
|
||||||
|
<box class="progress">
|
||||||
|
<box class="progress-fill" css={`min-width: ${Math.round(clamp(value) * 2.3)}px;`} />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SystemMonitor() {
|
||||||
|
return (
|
||||||
|
<box class="card" orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
||||||
|
<label class="card-title" xalign={0} label="System Monitoring" />
|
||||||
|
<Metric icon="" label="CPU" value={system.cpu} />
|
||||||
|
<Metric icon="" label="RAM" value={system.memory} />
|
||||||
|
<Metric icon="" label="Disk /" value={system.disk} />
|
||||||
|
<box class="meta-row">
|
||||||
|
<label hexpand xalign={0} label={` ${system.temp}`} />
|
||||||
|
<label xalign={1} label={` ${system.uptime}`} />
|
||||||
|
</box>
|
||||||
|
<label class="sparkline" xalign={0} label={sparkline(cpuHistory)} />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Calendar() {
|
||||||
|
const cells = monthDays();
|
||||||
|
const weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box class="card" orientation={Gtk.Orientation.VERTICAL} spacing={10}>
|
||||||
|
<label class="card-title" xalign={0} label={monthTitle()} />
|
||||||
|
<box class="calendar-grid" orientation={Gtk.Orientation.VERTICAL} spacing={5}>
|
||||||
|
<box spacing={5}>
|
||||||
|
{weekdays.map(day => <label class="weekday" label={day} />)}
|
||||||
|
</box>
|
||||||
|
{Array.from({ length: Math.ceil(cells.length / 7) }, (_unused, row) => (
|
||||||
|
<box spacing={5}>
|
||||||
|
{cells.slice(row * 7, row * 7 + 7).map(cell => (
|
||||||
|
<label
|
||||||
|
class={`day ${cell.today ? "today" : ""} ${cell.muted ? "muted" : ""}`}
|
||||||
|
label={cell.day}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Weather() {
|
||||||
|
return (
|
||||||
|
<box class="card weather" orientation={Gtk.Orientation.VERTICAL} spacing={10}>
|
||||||
|
<box>
|
||||||
|
<label class="card-title" hexpand xalign={0} label="Wetter" />
|
||||||
|
<button class="icon-button" tooltipText="Aktualisieren" onClicked={() => updateWeather(true)}>
|
||||||
|
<label label="" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
<label class="weather-main" xalign={0} wrap label={weather} />
|
||||||
|
<label class="card-subtitle" xalign={0} label="Quelle: wttr.in" />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hidePanel() {
|
||||||
|
const win = panelWindow || app.get_window("widget-panel");
|
||||||
|
win?.set_visible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PanelContent() {
|
||||||
|
return (
|
||||||
|
<box class="panel" orientation={Gtk.Orientation.VERTICAL} spacing={14}>
|
||||||
|
<box class="header">
|
||||||
|
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
|
||||||
|
<label class="title" xalign={0} label="Widgetbereich" />
|
||||||
|
<label class="subtitle" xalign={0} label="System, Kalender und Wetter" />
|
||||||
|
</box>
|
||||||
|
<button class="icon-button close" tooltipText="Schliessen" onClicked={hidePanel}>
|
||||||
|
<label label="" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
<SystemMonitor />
|
||||||
|
<Calendar />
|
||||||
|
<Weather />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function panelLayout() {
|
||||||
|
const geometry = app.monitors[0]?.get_geometry();
|
||||||
|
const screenWidth = geometry?.width || 1280;
|
||||||
|
const screenHeight = geometry?.height || 720;
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: Math.round(clamp(screenWidth * 0.28, 360, 460)),
|
||||||
|
height: Math.round(clamp(screenHeight - 96, 560, 900)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function WidgetPanelWindow() {
|
||||||
|
const layout = panelLayout();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<window
|
||||||
|
name="widget-panel"
|
||||||
|
namespace="widget-panel"
|
||||||
|
class="widget-window"
|
||||||
|
visible={!START_HIDDEN}
|
||||||
|
keymode={Astal.Keymode.EXCLUSIVE}
|
||||||
|
anchor={Astal.WindowAnchor.LEFT | Astal.WindowAnchor.TOP | Astal.WindowAnchor.BOTTOM}
|
||||||
|
application={app}
|
||||||
|
>
|
||||||
|
<Gtk.EventControllerKey onKeyPressed={(_, keyval) => {
|
||||||
|
if (keyval === ESC_KEYVAL) {
|
||||||
|
hidePanel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}} />
|
||||||
|
<box marginStart={18} marginTop={48} marginBottom={48}>
|
||||||
|
<scrolledwindow
|
||||||
|
class="panel-scroll"
|
||||||
|
widthRequest={layout.width}
|
||||||
|
heightRequest={layout.height}
|
||||||
|
hscrollbarPolicy={Gtk.PolicyType.NEVER}
|
||||||
|
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
>
|
||||||
|
<PanelContent />
|
||||||
|
</scrolledwindow>
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuild() {
|
||||||
|
const win = app.get_window("widget-panel");
|
||||||
|
if (!win) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeRebuild?.();
|
||||||
|
const layout = panelLayout();
|
||||||
|
createRoot(dispose => {
|
||||||
|
disposeRebuild = dispose;
|
||||||
|
win.set_child(
|
||||||
|
<box marginStart={18} marginTop={48} marginBottom={48}>
|
||||||
|
<scrolledwindow
|
||||||
|
class="panel-scroll"
|
||||||
|
widthRequest={layout.width}
|
||||||
|
heightRequest={layout.height}
|
||||||
|
hscrollbarPolicy={Gtk.PolicyType.NEVER}
|
||||||
|
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
>
|
||||||
|
<PanelContent />
|
||||||
|
</scrolledwindow>
|
||||||
|
</box> as Gtk.Widget,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimer() {
|
||||||
|
if (timerStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timerStarted = true;
|
||||||
|
updateSystem();
|
||||||
|
updateWeather(true);
|
||||||
|
|
||||||
|
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, REFRESH_SECONDS, () => {
|
||||||
|
updateSystem();
|
||||||
|
updateWeather();
|
||||||
|
rebuild();
|
||||||
|
return GLib.SOURCE_CONTINUE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.start({
|
||||||
|
instanceName: "widget-panel",
|
||||||
|
css: themeCss(activeTheme()),
|
||||||
|
main() {
|
||||||
|
panelWindow = WidgetPanelWindow() as Gtk.Window;
|
||||||
|
app.add_window(panelWindow);
|
||||||
|
if (!START_HIDDEN) {
|
||||||
|
panelWindow.present();
|
||||||
|
}
|
||||||
|
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
||||||
|
startTimer();
|
||||||
|
rebuild();
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -47,6 +47,7 @@ $menu = wofi
|
|||||||
exec-once = sh -c 'if command -v awww-daemon >/dev/null 2>&1; then awww-daemon; elif command -v swww-daemon >/dev/null 2>&1; then swww-daemon; else hyprpaper; fi'
|
exec-once = sh -c 'if command -v awww-daemon >/dev/null 2>&1; then awww-daemon; elif command -v swww-daemon >/dev/null 2>&1; then swww-daemon; else hyprpaper; fi'
|
||||||
exec-once = waybar
|
exec-once = waybar
|
||||||
exec-once = swaync
|
exec-once = swaync
|
||||||
|
exec-once = env WIDGET_PANEL_START_HIDDEN=1 ~/.config/hypr/Scripts/widget-panel.sh
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
### ENVIRONMENT VARIABLES ###
|
### ENVIRONMENT VARIABLES ###
|
||||||
@@ -204,7 +205,8 @@ bind = $mainMod, E, exec, $fileManager
|
|||||||
bind = $mainMod, N, exec, swaync-client -t
|
bind = $mainMod, N, exec, swaync-client -t
|
||||||
bind = $mainMod, V, togglefloating,
|
bind = $mainMod, V, togglefloating,
|
||||||
bind = $mainMod, R, exec, $menu
|
bind = $mainMod, R, exec, $menu
|
||||||
bind = $mainMod, W, exec, ~/.config/hypr/Scripts/ags-switcher.sh wallpaper
|
bind = $mainMod, W, exec, ~/.config/hypr/Scripts/widget-panel.sh
|
||||||
|
bind = $mainMod SHIFT, W, exec, ~/.config/hypr/Scripts/ags-switcher.sh wallpaper
|
||||||
bind = $mainMod, P, exec, ~/.config/hypr/Scripts/power-menu.py
|
bind = $mainMod, P, exec, ~/.config/hypr/Scripts/power-menu.py
|
||||||
bind = $mainMod, L, exec, hyprlock
|
bind = $mainMod, L, exec, hyprlock
|
||||||
bind = $mainMod SHIFT, T, exec, ~/.config/hypr/Scripts/ags-switcher.sh theme
|
bind = $mainMod SHIFT, T, exec, ~/.config/hypr/Scripts/ags-switcher.sh theme
|
||||||
|
|||||||
Reference in New Issue
Block a user