From 9b9b7b6824cb9219f93d1a70414b2412ea7d3585 Mon Sep 17 00:00:00 2001 From: sommerfeld Date: Thu, 17 Sep 2026 15:05:37 +0100 Subject: Add GNOME panel status and corporate desktop tools --- .../corporate-panel@dotfiles/extension.js | 145 +++++++++++++++++++++ .../corporate-panel@dotfiles/metadata.json | 7 + .../corporate-panel@dotfiles/stylesheet.css | 16 +++ 3 files changed, 168 insertions(+) create mode 100644 dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/extension.js create mode 100644 dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/metadata.json create mode 100644 dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/stylesheet.css (limited to 'dot_local/share') diff --git a/dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/extension.js b/dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/extension.js new file mode 100644 index 0000000..d959669 --- /dev/null +++ b/dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/extension.js @@ -0,0 +1,145 @@ +import Clutter from "gi://Clutter"; +import Gio from "gi://Gio"; +import GLib from "gi://GLib"; +import St from "gi://St"; +import { Extension } from "resource:///org/gnome/shell/extensions/extension.js"; +import * as Main from "resource:///org/gnome/shell/ui/main.js"; +import * as PanelMenu from "resource:///org/gnome/shell/ui/panelMenu.js"; +import * as PopupMenu from "resource:///org/gnome/shell/ui/popupMenu.js"; + +export default class CorporatePanel extends Extension { + enable() { + this._cancellable = new Gio.Cancellable(); + this._button = new PanelMenu.Button(0.0, "Corporate status"); + const box = new St.BoxLayout({ style_class: "corporate-panel" }); + this._labels = {}; + for (const name of ["displays", "updates", "failed", "reboot"]) { + const label = new St.Label({ + text: "", + y_align: Clutter.ActorAlign.CENTER, + }); + this._labels[name] = label; + box.add_child(label); + } + this._button.add_child(box); + this._addActions(); + Main.panel.addToStatusArea(this.uuid, this._button, 1, "right"); + this._refresh(); + this._timer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 60, () => { + this._refresh(); + return GLib.SOURCE_CONTINUE; + }); + } + + _command(...args) { + return [ + "/usr/bin/python3", + GLib.build_filenamev([ + GLib.get_home_dir(), + ".local/lib/dotfiles/canonical_panel.py", + ]), + ...args, + ]; + } + + _addActions() { + this._status = new PopupMenu.PopupMenuItem("Reading status...", { + reactive: false, + }); + this._button.menu.addMenuItem(this._status); + for (const [name, title] of [ + ["monitor", "System monitor"], + ["audio", "Audio mixer"], + ["displays", "Display settings"], + ["failed", "Failed services"], + ["updates", "Available updates (apt and Snap)"], + ["update", "Run dotfiles update"], + ["reboot", "Reboot requirement details"], + ["mail", "Thunderbird"], + ]) { + this._button.menu.addAction(title, () => { + try { + Gio.Subprocess.new( + this._command("action", name), + Gio.SubprocessFlags.NONE, + ); + } catch (error) { + Main.notifyError("Corporate panel", error.message); + } + }); + } + this._button.menu.addAction("Refresh status", () => this._refresh()); + } + + _refresh() { + if (this._process) return; + const token = this._cancellable; + try { + const process = Gio.Subprocess.new( + this._command("status"), + Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE, + ); + this._process = process; + process.communicate_utf8_async(null, token, (source, result) => { + if (token.is_cancelled()) return; + this._process = null; + try { + const [, stdout, stderr] = source.communicate_utf8_finish(result); + if (!source.get_successful()) + throw new Error(stderr || "Status command failed"); + this._render(JSON.parse(stdout)); + } catch (error) { + this._showError(error); + } + }); + } catch (error) { + this._showError(error); + } + } + + _render(status) { + for (const [name, label] of Object.entries(this._labels)) { + const value = status[name]; + label.set_text(value); + label.visible = Boolean(value); + const warning = + value.includes("?") || (name === "updates" && value !== "APT 0"); + const critical = + name === "reboot" || (name === "failed" && value !== "FAIL 0"); + label.set_style_class_name( + critical + ? "corporate-critical" + : warning + ? "corporate-warning" + : "corporate-ok", + ); + } + this._status.label.set_text( + status.errors + ? "Some checks failed; see journal" + : "APT uses the local package cache", + ); + if (status.errors) console.warn(`Corporate panel: ${status.errors}`); + } + + _showError(error) { + this._render({ + displays: "EXT ?", + updates: "APT ?", + failed: "FAIL ?", + reboot: "", + errors: error.message, + }); + } + + disable() { + this._cancellable?.cancel(); + if (this._timer) GLib.Source.remove(this._timer); + this._timer = null; + this._process?.force_exit(); + this._process = null; + this._button?.destroy(); + this._button = null; + this._labels = null; + } +} diff --git a/dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/metadata.json b/dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/metadata.json new file mode 100644 index 0000000..a21b618 --- /dev/null +++ b/dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/metadata.json @@ -0,0 +1,7 @@ +{ + "uuid": "corporate-panel@dotfiles", + "name": "Corporate Panel", + "description": "Display status, package updates, and failed services.", + "shell-version": ["50"], + "version": 1 +} diff --git a/dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/stylesheet.css b/dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/stylesheet.css new file mode 100644 index 0000000..a198ccc --- /dev/null +++ b/dot_local/share/gnome-shell/extensions/corporate-panel@dotfiles/stylesheet.css @@ -0,0 +1,16 @@ +.corporate-panel { + spacing: 8px; + font-size: 12px; +} + +.corporate-ok { + color: #b8bb26; +} + +.corporate-warning { + color: #fabd2f; +} + +.corporate-critical { + color: #fb4934; +} -- cgit v1.3.1