Files
NixOS-Configs/home/hypr/quickshell/GlobalState.qml
2026-08-31 21:20:26 -07:00

352 lines
14 KiB
QML

pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
QtObject {
id: root
// Modal / Overlay states
property bool spotlightVisible: false
property bool controlCenterVisible: false
property bool aboutMacVisible: false
property bool appleMenuVisible: false
property string activeDropdownMenu: ""
property bool volumePopupVisible: false
property bool wifiPopupVisible: false
property bool batteryPopupVisible: false
property bool calendarPopupVisible: false
// Active Window & Compositor info
property string activeWindowTitle: "Desktop"
property string activeWindowClass: "Finder"
property var runningApps: []
// Date & Time
property string timeString: "9:41 AM"
property string dateString: "Mon Oct 21"
property string fullDateString: "Monday, October 21, 2026"
// System Monitoring States
property int cpuPercent: 0
property int memPercent: 0
property string memUsedGb: "0.0"
property string memTotalGb: "0.0"
property int volume: 50
property bool volumeMuted: false
property int brightness: 100
property bool wifiConnected: true
property string connectionType: "off" // "wifi" | "eth" | "off"
property string wifiSsid: "Wi-Fi"
property string ipAddress: ""
property bool batteryPresent: false
property int batteryPercent: 100
property bool batteryCharging: false
property string batteryStatus: "Full"
// Disk Monitoring (root filesystem)
property int diskPercent: 0
property string diskUsedGb: "0"
property string diskTotalGb: "0"
// GPU VRAM Monitoring (NVIDIA)
property string gpuVramUsed: "0"
property string gpuVramTotal: "0"
property int gpuVramPercent: 0
// System HUD state (replaces individual popup states)
property bool systemHudVisible: false
property string systemHudActiveSection: "" // "volume", "wifi", "calendar", etc.
// Workspace tracking
property int activeWorkspace: 1
property var workspaceList: [1, 2, 3, 4, 5]
property var workspaceNames: ({1: "main", 2: "browser", 3: "code", 4: "media", 5: "misc"})
// Media Player State (MPRIS)
property string mediaTitle: ""
property string mediaArtist: ""
property bool mediaPlaying: false
// Close all popups and dropdowns
function closeAllPopups() {
appleMenuVisible = false;
activeDropdownMenu = "";
volumePopupVisible = false;
wifiPopupVisible = false;
batteryPopupVisible = false;
calendarPopupVisible = false;
systemHudVisible = false;
}
function toggleSpotlight() {
closeAllPopups();
spotlightVisible = !spotlightVisible;
}
function toggleControlCenter() {
closeAllPopups();
controlCenterVisible = !controlCenterVisible;
}
function toggleAboutMac() {
closeAllPopups();
aboutMacVisible = !aboutMacVisible;
}
function toggleSystemHud() {
closeAllPopups();
systemHudVisible = !systemHudVisible;
}
function switchWorkspace(n) {
// NOTE: Hyprland's Lua config bridge requires typed dispatcher calls
// (plain `hyprctl dispatch workspace 2` is mangled into invalid Lua).
Hyprland.dispatch("hl.dsp.focus({workspace = " + n + "})");
}
// Hyprland raw event processing
readonly property Connections hyprConn: Connections {
target: Hyprland
function onRawEvent(event) {
if (!event || !event.name) return;
const name = event.name;
const data = event.data !== undefined ? String(event.data) : "";
if (name === "workspace") {
const wsNum = parseInt(data);
if (!isNaN(wsNum)) root.activeWorkspace = wsNum;
} else if (name === "activewindow") {
const parts = data.split(",");
const cls = parts[0] ? parts[0].trim() : "";
const title = parts[1] ? parts[1].trim() : (cls || "");
root.activeWindowClass = cls && cls.length > 0 ? cls : "Desktop";
root.activeWindowTitle = title && title.length > 0 ? title : root.activeWindowClass;
} else if (name === "activewindowv2" || name === "openwindow" || name === "closewindow") {
// Update running clients
clientsProc.running = true;
}
}
}
// Time Updater Timer
readonly property Timer clockTimer: Timer {
interval: 1000
running: true
repeat: true
triggeredOnStart: true
onTriggered: {
const now = new Date();
root.timeString = Qt.formatDateTime(now, "h:mm AP");
root.dateString = Qt.formatDateTime(now, "ddd MMM d");
root.fullDateString = Qt.formatDateTime(now, "dddd, MMMM d, yyyy");
}
}
// System Stats Poll Timer
readonly property Timer statsTimer: Timer {
interval: 3000
running: true
repeat: true
triggeredOnStart: true
onTriggered: {
cpuProc.running = true;
memProc.running = true;
batProc.running = true;
wifiProc.running = true;
audioProc.running = true;
brightProc.running = true;
mprisProc.running = true;
gpuProc.running = true;
diskProc.running = true;
}
}
// Background Process Listeners
readonly property Process cpuProc: Process {
command: ["bash", "-c", "read -r c u n s i io ir st g gn < /proc/stat; t1=$((u+n+s+i+io+ir+st+g+gn)); i1=$((i+io)); sleep 0.15; read -r c u n s i io ir st g gn < /proc/stat; t2=$((u+n+s+i+io+ir+st+g+gn)); i2=$((i+io)); td=$((t2-t1)); id=$((i2-i1)); [ $td -gt 0 ] && echo $(( (td-id)*100/td )) || echo 0"]
stdout: StdioCollector {
onDataChanged: {
const val = parseInt(String(data).trim());
if (!isNaN(val)) root.cpuPercent = Math.max(0, Math.min(100, val));
}
}
}
readonly property Process memProc: Process {
command: ["bash", "-c", "t=$(grep MemTotal /proc/meminfo | awk '{print $2}'); a=$(grep MemAvailable /proc/meminfo | awk '{print $2}'); if [ -n \"$t\" ] && [ -n \"$a\" ] && [ \"$t\" -gt 0 ]; then u=$((t-a)); p=$((u*100/t)); ug=$(awk \"BEGIN {printf \\\"%.1f\\\", $u/1048576}\"); tg=$(awk \"BEGIN {printf \\\"%.1f\\\", $t/1048576}\"); echo \"$p $ug $tg\"; fi"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(" ");
if (parts.length >= 3) {
root.memPercent = parseInt(parts[0]) || 0;
root.memUsedGb = parts[1];
root.memTotalGb = parts[2];
}
}
}
}
readonly property Process batProc: Process {
command: ["bash", "-c", "b=$(find /sys/class/power_supply/ -name 'BAT*' 2>/dev/null | head -n 1); if [ -n \"$b\" ]; then cap=$(cat $b/capacity 2>/dev/null || echo 100); st=$(cat $b/status 2>/dev/null || echo Full); echo \"1 $cap $st\"; else echo \"0 100 AC\"; fi"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(" ");
if (parts.length >= 3) {
root.batteryPresent = parts[0] === "1";
root.batteryPercent = parseInt(parts[1]) || 100;
root.batteryStatus = parts[2];
root.batteryCharging = parts[2] === "Charging" || parts[2] === "Full";
}
}
}
}
readonly property Process wifiProc: Process {
// Emits: connectionType|ssid|ip ("wifi" | "eth" | "off")
command: ["bash", "-c", "route=$(ip -4 route get 1.1.1.1 2>/dev/null); ip=$(echo \"$route\" | awk '{for(i=1;i<=NF;i++) if($i==\"src\") print $(i+1)}'); dev=$(echo \"$route\" | awk '{for(i=1;i<=NF;i++) if($i==\"dev\") print $(i+1)}'); type=\"off\"; ssid=\"\"; if [ -n \"$dev\" ]; then if [ -d \"/sys/class/net/$dev/wireless\" ]; then type=\"wifi\"; ssid=$(iwgetid -r 2>/dev/null); if [ -z \"$ssid\" ] && command -v nmcli &>/dev/null; then ssid=$(nmcli -t -f active,ssid dev wifi 2>/dev/null | grep '^yes:' | cut -d':' -f2); fi; else type=\"eth\"; fi; fi; echo \"$type|$ssid|$ip\""]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split("|");
if (parts.length >= 3) {
root.connectionType = parts[0] !== "" ? parts[0] : "off";
root.wifiSsid = parts[1];
root.ipAddress = parts[2];
root.wifiConnected = parts[2] !== "" && parts[2] !== "Offline";
}
}
}
}
readonly property Process audioProc: Process {
command: ["bash", "-c", "if command -v pactl &>/dev/null; then v=$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -Po '[0-9]+(?=%)' | head -n 1); m=$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | grep -q yes && echo 1 || echo 0); echo \"${v:-50} $m\"; fi"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(" ");
if (parts.length >= 2) {
root.volume = parseInt(parts[0]) || 50;
root.volumeMuted = parts[1] === "1";
}
}
}
}
readonly property Process brightProc: Process {
command: ["bash", "-c", "if command -v brightnessctl &>/dev/null; then b=$(brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%'); echo \"${b:-100}\"; else echo 100; fi"]
stdout: StdioCollector {
onDataChanged: {
const val = parseInt(String(data).trim());
if (!isNaN(val)) root.brightness = val;
}
}
}
readonly property Process diskProc: Process {
// Emits: usedGB totalGB percent for the root filesystem
command: ["bash", "-c", "df -BG / 2>/dev/null | awk 'NR==2 {u=$3; t=$2; gsub(/G/,\"\",u); gsub(/G/,\"\",t); if (t+0 > 0) print u, t, int(u*100/t)}'"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(" ");
if (parts.length >= 3) {
root.diskUsedGb = parts[0];
root.diskTotalGb = parts[1];
root.diskPercent = parseInt(parts[2]) || 0;
}
}
}
}
readonly property Process mprisProc: Process {
command: ["bash", "-c", "if command -v playerctl &>/dev/null; then t=$(playerctl metadata title 2>/dev/null); a=$(playerctl metadata artist 2>/dev/null); s=$(playerctl status 2>/dev/null); echo \"$s|$t|$a\"; fi"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split("|");
if (parts.length >= 3) {
root.mediaPlaying = parts[0] === "Playing";
root.mediaTitle = parts[1] || "";
root.mediaArtist = parts[2] || "";
} else {
root.mediaPlaying = false;
root.mediaTitle = "";
root.mediaArtist = "";
}
}
}
}
readonly property Process gpuProc: Process {
command: ["bash", "-c", "nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits 2>/dev/null || echo '0, 0'"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(",");
if (parts.length >= 2) {
const usedMiB = parseInt(parts[0].trim());
const totalMiB = parseInt(parts[1].trim());
if (!isNaN(usedMiB) && !isNaN(totalMiB) && totalMiB > 0) {
root.gpuVramUsed = (usedMiB / 1024).toFixed(1);
root.gpuVramTotal = (totalMiB / 1024).toFixed(1);
root.gpuVramPercent = Math.round((usedMiB / totalMiB) * 100);
}
}
}
}
}
readonly property Process clientsProc: Process {
command: ["bash", "-c", "if command -v hyprctl &>/dev/null; then hyprctl -j clients 2>/dev/null; else echo '[]'; fi"]
stdout: StdioCollector {
onDataChanged: {
try {
const parsed = JSON.parse(String(data).trim());
if (Array.isArray(parsed)) {
root.runningApps = parsed;
}
} catch(e) {}
}
}
}
// Helper functions to control system (execDetached: fire-and-forget)
function setVolume(val) {
root.volume = Math.max(0, Math.min(100, val));
Quickshell.execDetached(["pactl", "set-sink-volume", "@DEFAULT_SINK@", `${root.volume}%`]);
}
function toggleVolumeMute() {
Quickshell.execDetached(["pactl", "set-sink-mute", "@DEFAULT_SINK@", "toggle"]);
root.volumeMuted = !root.volumeMuted;
}
function setBrightness(val) {
root.brightness = Math.max(1, Math.min(100, val));
Quickshell.execDetached(["brightnessctl", "set", `${root.brightness}%`]);
}
function mediaPlayPause() {
Quickshell.execDetached(["playerctl", "play-pause"]);
mprisProc.running = true;
}
function mediaNext() {
Quickshell.execDetached(["playerctl", "next"]);
mprisProc.running = true;
}
function mediaPrevious() {
Quickshell.execDetached(["playerctl", "previous"]);
mprisProc.running = true;
}
function launchApp(execCmd) {
if (!execCmd) return;
Quickshell.execDetached(["sh", "-c", execCmd]);
closeAllPopups();
spotlightVisible = false;
}
}