Sync current Hyprland widgets

This commit is contained in:
Pascal
2026-04-28 18:25:20 +02:00
parent b4e4081f25
commit 25f0e3653e
8 changed files with 925 additions and 19 deletions

View File

@@ -94,6 +94,42 @@ entry:focus {
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 {
min-height: 420px;
}

View File

@@ -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

View 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;
}

View 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;
});
},
});