281 lines
10 KiB
QML
281 lines
10 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 wifiSsid: "Wi-Fi"
|
|
property string ipAddress: ""
|
|
|
|
property bool batteryPresent: false
|
|
property int batteryPercent: 100
|
|
property bool batteryCharging: false
|
|
property string batteryStatus: "Full"
|
|
|
|
// 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;
|
|
}
|
|
|
|
function toggleSpotlight() {
|
|
closeAllPopups();
|
|
spotlightVisible = !spotlightVisible;
|
|
}
|
|
|
|
function toggleControlCenter() {
|
|
closeAllPopups();
|
|
controlCenterVisible = !controlCenterVisible;
|
|
}
|
|
|
|
function toggleAboutMac() {
|
|
closeAllPopups();
|
|
aboutMacVisible = !aboutMacVisible;
|
|
}
|
|
|
|
// Hyprland raw event processing
|
|
readonly property Connections hyprConn: Connections {
|
|
target: Hyprland
|
|
function onRawEvent(event) {
|
|
if (!event) return;
|
|
if (event.startsWith("activewindow>>")) {
|
|
const data = event.substring(14);
|
|
const parts = data.split(",");
|
|
const title = parts[1] ? parts[1].trim() : (parts[0] ? parts[0].trim() : "");
|
|
const cls = parts[0] ? parts[0].trim() : "Finder";
|
|
|
|
root.activeWindowClass = cls && cls.length > 0 ? cls : "Finder";
|
|
root.activeWindowTitle = title && title.length > 0 ? title : root.activeWindowClass;
|
|
} else if (event.startsWith("activewindowv2>>")) {
|
|
// Update running clients
|
|
clientsProc.running = true;
|
|
} else if (event.startsWith("openwindow>>") || event.startsWith("closewindow>>")) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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(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 = 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 = 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 {
|
|
command: ["bash", "-c", "ssid=\"\"; if command -v nmcli &>/dev/null; then ssid=$(nmcli -t -f active,ssid dev wifi 2>/dev/null | grep '^yes:' | cut -d':' -f2); fi; [ -z \"$ssid\" ] && [ -f /proc/net/wireless ] && ssid=$(iwgetid -r 2>/dev/null); ip=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}'); echo \"${ssid:-Ethernet} ${ip:-Offline}\""]
|
|
stdout: StdioCollector {
|
|
onDataChanged: {
|
|
const str = data.trim();
|
|
const parts = str.split(" ");
|
|
if (parts.length >= 2) {
|
|
root.wifiSsid = parts[0];
|
|
root.ipAddress = parts[1];
|
|
root.wifiConnected = parts[1] !== "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 = 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(data.trim());
|
|
if (!isNaN(val)) root.brightness = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = 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 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(data.trim());
|
|
if (Array.isArray(parsed)) {
|
|
root.runningApps = parsed;
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper functions to control system
|
|
function setVolume(val) {
|
|
root.volume = Math.max(0, Math.min(100, val));
|
|
Process.exec(["pactl", "set-sink-volume", "@DEFAULT_SINK@", `${root.volume}%`]);
|
|
}
|
|
|
|
function toggleVolumeMute() {
|
|
Process.exec(["pactl", "set-sink-mute", "@DEFAULT_SINK@", "toggle"]);
|
|
root.volumeMuted = !root.volumeMuted;
|
|
}
|
|
|
|
function setBrightness(val) {
|
|
root.brightness = Math.max(1, Math.min(100, val));
|
|
Process.exec(["brightnessctl", "set", `${root.brightness}%`]);
|
|
}
|
|
|
|
function mediaPlayPause() {
|
|
Process.exec(["playerctl", "play-pause"]);
|
|
mprisProc.running = true;
|
|
}
|
|
|
|
function mediaNext() {
|
|
Process.exec(["playerctl", "next"]);
|
|
mprisProc.running = true;
|
|
}
|
|
|
|
function mediaPrevious() {
|
|
Process.exec(["playerctl", "previous"]);
|
|
mprisProc.running = true;
|
|
}
|
|
|
|
function launchApp(execCmd) {
|
|
if (!execCmd) return;
|
|
Process.exec(["bash", "-c", `${execCmd} &`]);
|
|
closeAllPopups();
|
|
spotlightVisible = false;
|
|
}
|
|
}
|