Compare commits

..

3 commits

6 changed files with 643 additions and 42 deletions

View file

@ -10,15 +10,19 @@ M.HoverPanel {
contentWidth: 250 contentWidth: 250
property var _devices: [] property var _devices: []
property bool _btEnabled: true
property Process _scanner: Process { property Process _scanner: Process {
id: scanner id: scanner
running: true running: true
command: ["sh", "-c", "bluetoothctl devices Paired 2>/dev/null | while read -r _ mac name; do " + "info=$(bluetoothctl info \"$mac\" 2>/dev/null); " + "conn=$(echo \"$info\" | grep -c 'Connected: yes'); " + "bat=$(echo \"$info\" | awk -F'[(): ]' '/Battery Percentage/{for(i=1;i<=NF;i++) if($i+0==$i && $i!=\"\") print $i}'); " + "echo \"$mac:$conn:${bat:-}:$name\"; " + "done"] command: ["sh", "-c", "bluetoothctl show 2>/dev/null | awk '/Powered:/{print $2; exit}';" + "echo '---DEVICES---';" + "bluetoothctl devices Paired 2>/dev/null | while read -r _ mac name; do " + "info=$(bluetoothctl info \"$mac\" 2>/dev/null); " + "conn=$(echo \"$info\" | grep -c 'Connected: yes'); " + "bat=$(echo \"$info\" | awk -F'[(): ]' '/Battery Percentage/{for(i=1;i<=NF;i++) if($i+0==$i && $i!=\"\") print $i}'); " + "echo \"$mac:$conn:${bat:-}:$name\"; " + "done"]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
const sections = text.split("---DEVICES---");
menuWindow._btEnabled = (sections[0] || "").trim() === "yes";
const devs = []; const devs = [];
for (const line of text.trim().split("\n")) { for (const line of (sections[1] || "").trim().split("\n")) {
if (!line) if (!line)
continue; continue;
const i1 = line.indexOf(":"); const i1 = line.indexOf(":");
@ -27,10 +31,10 @@ M.HoverPanel {
if (i3 < 0) if (i3 < 0)
continue; continue;
devs.push({ devs.push({
mac: line.slice(0, i1), "mac": line.slice(0, i1),
connected: line.slice(i1 + 1, i2) === "1", "connected": line.slice(i1 + 1, i2) === "1",
battery: parseInt(line.slice(i2 + 1, i3)) || -1, "battery": parseInt(line.slice(i2 + 1, i3)) || -1,
name: line.slice(i3 + 1) "name": line.slice(i3 + 1)
}); });
} }
devs.sort((a, b) => { devs.sort((a, b) => {
@ -43,6 +47,14 @@ M.HoverPanel {
} }
} }
property Process _powerProc: Process {
id: powerProc
property string _action: ""
command: ["bluetoothctl", "power", _action]
onRunningChanged: if (!running)
scanner.running = true
}
property Process _toggleProc: Process { property Process _toggleProc: Process {
id: toggleProc id: toggleProc
property string action: "" property string action: ""
@ -52,6 +64,70 @@ M.HoverPanel {
scanner.running = true scanner.running = true
} }
// Bluetooth radio toggle header
Item {
width: menuWindow.contentWidth
height: 36
Rectangle {
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
color: btHeaderArea.containsMouse ? M.Theme.base02 : "transparent"
radius: M.Theme.radius
}
Text {
id: btHeaderIcon
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "\uF294"
color: menuWindow._btEnabled ? menuWindow.accentColor : M.Theme.base04
font.pixelSize: M.Theme.fontSize + 1
font.family: M.Theme.iconFontFamily
}
Text {
anchors.left: btHeaderIcon.right
anchors.leftMargin: 8
anchors.verticalCenter: parent.verticalCenter
text: "Bluetooth"
color: menuWindow._btEnabled ? M.Theme.base05 : M.Theme.base04
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily
font.bold: true
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "\uF011"
color: menuWindow._btEnabled ? menuWindow.accentColor : M.Theme.base04
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.iconFontFamily
}
MouseArea {
id: btHeaderArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
powerProc._action = menuWindow._btEnabled ? "off" : "on";
powerProc.running = true;
}
}
}
Rectangle {
width: menuWindow.contentWidth - 16
height: 1
anchors.horizontalCenter: parent.horizontalCenter
color: M.Theme.base03
}
Repeater { Repeater {
model: menuWindow._devices model: menuWindow._devices
@ -60,7 +136,7 @@ M.HoverPanel {
required property var modelData required property var modelData
required property int index required property int index
width: menuWindow.panelWidth width: menuWindow.contentWidth
height: 32 height: 32
Rectangle { Rectangle {
@ -125,11 +201,11 @@ M.HoverPanel {
Text { Text {
visible: menuWindow._devices.length === 0 visible: menuWindow._devices.length === 0
width: menuWindow.panelWidth width: menuWindow.contentWidth
height: 32 height: 32
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
text: "No paired devices" text: menuWindow._btEnabled ? "No paired devices" : "Bluetooth is off"
color: M.Theme.base04 color: M.Theme.base04
font.pixelSize: M.Theme.fontSize font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily font.family: M.Theme.fontFamily

View file

@ -5,26 +5,42 @@ import "." as M
M.BarSection { M.BarSection {
id: root id: root
spacing: Math.max(1, M.Theme.moduleSpacing - 2) spacing: Math.max(1, M.Theme.moduleSpacing - 2)
tooltip: root.freePct + "% free of " + root.totalTb.toFixed(1) + " TB" tooltip: ""
property int freePct: 0 property var _mounts: []
property real totalTb: 0 property int _rootPct: 0
Process { Process {
id: proc id: proc
running: true running: true
command: ["sh", "-c", "df -B1 --output=size,avail / | tail -1"] command: ["sh", "-c", "df -x tmpfs -x devtmpfs -x squashfs -x efivarfs -x overlay -B1 --output=target,size,used 2>/dev/null | awk 'NR>1 && $2+0>0 {print $1\"|\"$2\"|\"$3}'"]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
const parts = text.trim().split(/\s+/).map(Number); const lines = text.trim().split("\n").filter(l => l);
const size = parts[0], avail = parts[1]; const mounts = [];
if (size > 0) { for (const line of lines) {
root.freePct = Math.round((avail / size) * 100); const parts = line.split("|");
root.totalTb = size / 1e12; if (parts.length < 3)
} continue;
const total = parseInt(parts[1]);
const used = parseInt(parts[2]);
if (total <= 0)
continue;
mounts.push({
"target": parts[0],
"pct": Math.round(used / total * 100),
"usedBytes": used,
"totalBytes": total
});
}
root._mounts = mounts;
const rm = mounts.find(m => m.target === "/");
if (rm)
root._rootPct = rm.pct;
} }
} }
} }
Timer { Timer {
interval: M.Modules.disk.interval || 30000 interval: M.Modules.disk.interval || 30000
running: true running: true
@ -32,15 +48,150 @@ M.BarSection {
onTriggered: proc.running = true onTriggered: proc.running = true
} }
function _fmt(bytes) {
if (bytes >= 1e12)
return (bytes / 1e12).toFixed(1) + "T";
if (bytes >= 1e9)
return Math.round(bytes / 1e9) + "G";
if (bytes >= 1e6)
return Math.round(bytes / 1e6) + "M";
return bytes + "B";
}
function _barColor(pct) {
const t = Math.max(0, Math.min(100, pct)) / 100;
const a = t < 0.5 ? M.Theme.base0B : M.Theme.base0A;
const b = t < 0.5 ? M.Theme.base0A : M.Theme.base08;
const u = t < 0.5 ? t * 2 : (t - 0.5) * 2;
return Qt.rgba(a.r + (b.r - a.r) * u, a.g + (b.g - a.g) * u, a.b + (b.b - a.b) * u, 1);
}
property bool _pinned: false
readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered
readonly property bool _showPanel: _anyHover || _pinned
on_AnyHoverChanged: {
if (_anyHover)
_unpinTimer.stop();
else if (_pinned)
_unpinTimer.start();
}
Timer {
id: _unpinTimer
interval: 500
onTriggered: root._pinned = false
}
M.BarIcon { M.BarIcon {
icon: "\uF0C9" icon: "\uF0C9"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
TapHandler {
cursorShape: Qt.PointingHandCursor
onTapped: root._pinned = !root._pinned
}
} }
M.BarLabel { M.BarLabel {
label: root.freePct + "% " + root.totalTb.toFixed(1) label: root._rootPct + "%"
minText: "100% 9.9" minText: "100%"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
TapHandler {
cursorShape: Qt.PointingHandCursor
onTapped: root._pinned = !root._pinned
}
}
M.HoverPanel {
id: hoverPanel
showPanel: root._showPanel
screen: QsWindow.window?.screen ?? null
anchorItem: root
accentColor: root.accentColor
panelNamespace: "nova-disk"
contentWidth: 260
Item {
width: parent.width
height: 28
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Disk"
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 1
font.family: M.Theme.fontFamily
}
}
Repeater {
model: root._mounts
delegate: Item {
required property var modelData
width: hoverPanel.contentWidth
height: 22
Text {
id: mountLabel
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: modelData.target
color: M.Theme.base05
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
elide: Text.ElideRight
width: 72
}
Item {
id: mountBar
anchors.left: mountLabel.right
anchors.leftMargin: 6
anchors.right: sizeLabel.left
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter
height: 4
Rectangle {
anchors.fill: parent
color: M.Theme.base02
radius: 2
}
Rectangle {
width: parent.width * (modelData.pct / 100)
height: parent.height
color: root._barColor(modelData.pct)
radius: 2
Behavior on width {
NumberAnimation {
duration: 200
}
}
}
}
Text {
id: sizeLabel
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root._fmt(modelData.usedBytes) + "/" + root._fmt(modelData.totalBytes)
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
width: 72
horizontalAlignment: Text.AlignRight
}
}
}
Item {
width: 1
height: 4
}
} }
} }

View file

@ -5,9 +5,14 @@ import "." as M
M.BarSection { M.BarSection {
id: root id: root
spacing: Math.max(1, M.Theme.moduleSpacing - 2) spacing: Math.max(1, M.Theme.moduleSpacing - 2)
tooltip: "Memory: " + root.percent + "% used" tooltip: ""
property int percent: 0 property int percent: 0
property real usedGb: 0
property real totalGb: 0
property real availGb: 0
property real cachedGb: 0
property real buffersGb: 0
FileView { FileView {
id: meminfo id: meminfo
@ -19,12 +24,22 @@ M.BarSection {
if (v) if (v)
m[k.trim()] = parseInt(v.trim()); m[k.trim()] = parseInt(v.trim());
}); });
const total = m.MemTotal; const total = m.MemTotal || 0;
const avail = m.MemAvailable; const avail = m.MemAvailable || 0;
if (total > 0) const buffers = m.Buffers || 0;
root.percent = Math.round(((total - avail) / total) * 100); const cached = (m.Cached || 0) + (m.SReclaimable || 0);
const used = total - avail;
if (total > 0) {
root.percent = Math.round(used / total * 100);
root.usedGb = used / 1048576;
root.totalGb = total / 1048576;
root.availGb = avail / 1048576;
root.cachedGb = cached / 1048576;
root.buffersGb = buffers / 1048576;
} }
} }
}
Timer { Timer {
interval: M.Modules.memory.interval || 2000 interval: M.Modules.memory.interval || 2000
running: true running: true
@ -32,15 +47,253 @@ M.BarSection {
onTriggered: meminfo.reload() onTriggered: meminfo.reload()
} }
function _fmt(gb) {
return gb >= 10 ? gb.toFixed(1) + "G" : gb.toFixed(2) + "G";
}
property bool _pinned: false
readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered
readonly property bool _showPanel: _anyHover || _pinned
on_AnyHoverChanged: {
if (_anyHover)
_unpinTimer.stop();
else if (_pinned)
_unpinTimer.start();
}
Timer {
id: _unpinTimer
interval: 500
onTriggered: root._pinned = false
}
M.BarIcon { M.BarIcon {
icon: "\uEFC5" icon: "\uEFC5"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
TapHandler {
cursorShape: Qt.PointingHandCursor
onTapped: root._pinned = !root._pinned
}
} }
M.BarLabel { M.BarLabel {
label: root.percent + "%" label: root.percent + "%"
minText: "100%" minText: "100%"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
TapHandler {
cursorShape: Qt.PointingHandCursor
onTapped: root._pinned = !root._pinned
}
}
M.HoverPanel {
id: hoverPanel
showPanel: root._showPanel
screen: QsWindow.window?.screen ?? null
anchorItem: root
accentColor: root.accentColor
panelNamespace: "nova-memory"
contentWidth: 240
// Header
Item {
width: parent.width
height: 28
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Memory"
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 1
font.family: M.Theme.fontFamily
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root._fmt(root.usedGb) + " / " + root._fmt(root.totalGb)
color: root.accentColor
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily
font.bold: true
}
}
// Usage bar
Item {
width: parent.width
height: 14
Item {
id: memBar
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
height: 6
Rectangle {
anchors.fill: parent
color: M.Theme.base02
radius: 3
}
// Cached (base0D, behind used)
Rectangle {
width: parent.width * Math.min(1, (root.usedGb + root.cachedGb) / Math.max(root.totalGb, 0.001))
height: parent.height
color: M.Theme.base0D
opacity: 0.4
radius: 3
Behavior on width {
NumberAnimation {
duration: 200
}
}
}
// Used (accentColor, on top)
Rectangle {
width: parent.width * Math.min(1, root.usedGb / Math.max(root.totalGb, 0.001))
height: parent.height
color: root.accentColor
radius: 3
Behavior on width {
NumberAnimation {
duration: 200
}
}
}
}
}
// Breakdown rows
Item {
width: parent.width
height: 18
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Used"
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root._fmt(root.usedGb)
color: M.Theme.base05
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
}
}
Item {
width: parent.width
height: 18
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Cached"
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root._fmt(root.cachedGb)
color: M.Theme.base05
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
}
}
Item {
width: parent.width
height: 18
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "Available"
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root._fmt(root.availGb)
color: M.Theme.base05
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
}
}
// Process list separator
Rectangle {
width: parent.width - 16
height: 1
anchors.horizontalCenter: parent.horizontalCenter
color: M.Theme.base03
}
// Top processes by memory
Repeater {
model: M.ProcessList.byMem
delegate: Item {
required property var modelData
width: hoverPanel.contentWidth
height: 20
Text {
id: procCmd
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: modelData.cmd
color: M.Theme.base05
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
elide: Text.ElideRight
width: parent.width - 80
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: modelData.mem.toFixed(1) + "%"
color: M.Theme.base04
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
width: 36
horizontalAlignment: Text.AlignRight
}
}
}
Item {
width: 1
height: 4
}
} }
} }

View file

@ -10,18 +10,21 @@ M.HoverPanel {
contentWidth: 250 contentWidth: 250
property var _networks: [] property var _networks: []
property bool _wifiEnabled: true
property Process _scanner: Process { property Process _scanner: Process {
id: scanner id: scanner
running: true running: true
command: ["sh", "-c", "echo '---CONNS---';" + "nmcli -t -f NAME,UUID,TYPE,ACTIVE connection show 2>/dev/null;" + "echo '---WIFI---';" + "nmcli -t -f SSID,SIGNAL device wifi list --rescan no 2>/dev/null"] command: ["sh", "-c", "echo '---RADIO---';" + "nmcli radio wifi 2>/dev/null;" + "echo '---CONNS---';" + "nmcli -t -f NAME,UUID,TYPE,ACTIVE connection show 2>/dev/null;" + "echo '---WIFI---';" + "nmcli -t -f SSID,SIGNAL device wifi list --rescan no 2>/dev/null"]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
const radioSection = text.split("---CONNS---")[0].split("---RADIO---")[1] || "";
menuWindow._wifiEnabled = radioSection.trim() === "enabled";
const sections = text.split("---WIFI---"); const sections = text.split("---WIFI---");
const connLines = (sections[0] || "").split("---CONNS---")[1] || ""; const connLines = (sections[0] || "").split("---CONNS---")[1] || "";
const wifiLines = sections[1] || ""; const wifiLines = sections[1] || "";
// Visible SSIDs with signal
const visible = {}; const visible = {};
for (const l of wifiLines.trim().split("\n")) { for (const l of wifiLines.trim().split("\n")) {
if (!l) if (!l)
@ -32,7 +35,6 @@ M.HoverPanel {
visible[ssid] = parseInt(parts[1]) || 0; visible[ssid] = parseInt(parts[1]) || 0;
} }
// Saved connections filter: show wired always, wifi only if visible
const nets = []; const nets = [];
for (const l of connLines.trim().split("\n")) { for (const l of connLines.trim().split("\n")) {
if (!l) if (!l)
@ -48,15 +50,14 @@ M.HoverPanel {
continue; continue;
nets.push({ nets.push({
name: name, "name": name,
uuid: uuid, "uuid": uuid,
isWifi: isWifi, "isWifi": isWifi,
active: active, "active": active,
signal: isWifi ? (visible[name] || 0) : -1 "signal": isWifi ? (visible[name] || 0) : -1
}); });
} }
// Active first, then by signal (wifi) or name
nets.sort((a, b) => { nets.sort((a, b) => {
if (a.active !== b.active) if (a.active !== b.active)
return a.active ? -1 : 1; return a.active ? -1 : 1;
@ -70,6 +71,14 @@ M.HoverPanel {
} }
} }
property Process _radioProc: Process {
id: radioProc
property string _state: ""
command: ["nmcli", "radio", "wifi", _state]
onRunningChanged: if (!running)
scanner.running = true
}
property Process _connectProc: Process { property Process _connectProc: Process {
id: connectProc id: connectProc
property string uuid: "" property string uuid: ""
@ -86,6 +95,70 @@ M.HoverPanel {
scanner.running = true scanner.running = true
} }
// Wi-Fi radio toggle header
Item {
width: menuWindow.contentWidth
height: 36
Rectangle {
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
color: headerArea.containsMouse ? M.Theme.base02 : "transparent"
radius: M.Theme.radius
}
Text {
id: wifiHeaderIcon
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "\uF1EB"
color: menuWindow._wifiEnabled ? menuWindow.accentColor : M.Theme.base04
font.pixelSize: M.Theme.fontSize + 1
font.family: M.Theme.iconFontFamily
}
Text {
anchors.left: wifiHeaderIcon.right
anchors.leftMargin: 8
anchors.verticalCenter: parent.verticalCenter
text: "Wi-Fi"
color: menuWindow._wifiEnabled ? M.Theme.base05 : M.Theme.base04
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily
font.bold: true
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "\uF011"
color: menuWindow._wifiEnabled ? menuWindow.accentColor : M.Theme.base04
font.pixelSize: M.Theme.fontSize
font.family: M.Theme.iconFontFamily
}
MouseArea {
id: headerArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
radioProc._state = menuWindow._wifiEnabled ? "off" : "on";
radioProc.running = true;
}
}
}
Rectangle {
width: menuWindow.contentWidth - 16
height: 1
anchors.horizontalCenter: parent.horizontalCenter
color: M.Theme.base03
}
Repeater { Repeater {
model: menuWindow._networks model: menuWindow._networks
@ -94,7 +167,7 @@ M.HoverPanel {
required property var modelData required property var modelData
required property int index required property int index
width: menuWindow.panelWidth width: menuWindow.contentWidth
height: 32 height: 32
Rectangle { Rectangle {
@ -161,14 +234,13 @@ M.HoverPanel {
} }
} }
// Empty state
Text { Text {
visible: menuWindow._networks.length === 0 visible: menuWindow._networks.length === 0
width: menuWindow.panelWidth width: menuWindow.contentWidth
height: 32 height: 32
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
text: "No networks available" text: menuWindow._wifiEnabled ? "No networks available" : "Wi-Fi is off"
color: M.Theme.base04 color: M.Theme.base04
font.pixelSize: M.Theme.fontSize font.pixelSize: M.Theme.fontSize
font.family: M.Theme.fontFamily font.family: M.Theme.fontFamily

48
modules/ProcessList.qml Normal file
View file

@ -0,0 +1,48 @@
pragma Singleton
import QtQuick
import Quickshell.Io
import "." as M
QtObject {
id: root
property var byCpu: []
property var byMem: []
property int maxItems: 8
property Process _proc: Process {
id: proc
running: true
command: ["sh", "-c", "ps aux --sort=-%cpu 2>/dev/null | awk 'NR>1 && NR<=50 {cmd=$11; for(i=12;i<=NF&&i<=13;i++) cmd=cmd\" \"$i; print $1\"|\"$2\"|\"$3\"|\"$4\"|\"cmd}'"]
stdout: StdioCollector {
onStreamFinished: {
const rows = [];
for (const line of text.trim().split("\n")) {
if (!line)
continue;
const p = line.split("|");
if (p.length < 5)
continue;
const cmd = p[4].replace(/^.*\//, "");
rows.push({
"user": p[0],
"pid": parseInt(p[1]),
"cpu": parseFloat(p[2]),
"mem": parseFloat(p[3]),
"cmd": cmd || p[4]
});
}
root.byCpu = rows.slice().sort((a, b) => b.cpu - a.cpu).slice(0, root.maxItems);
root.byMem = rows.slice().sort((a, b) => b.mem - a.mem).slice(0, root.maxItems);
}
}
}
property Timer _timer: Timer {
interval: 2000
running: true
repeat: true
onTriggered: proc.running = true
}
}

View file

@ -32,6 +32,7 @@ Weather 1.0 Weather.qml
PowerProfile 1.0 PowerProfile.qml PowerProfile 1.0 PowerProfile.qml
IdleInhibitor 1.0 IdleInhibitor.qml IdleInhibitor 1.0 IdleInhibitor.qml
Notifications 1.0 Notifications.qml Notifications 1.0 Notifications.qml
singleton ProcessList 1.0 ProcessList.qml
singleton NotifService 1.0 NotifService.qml singleton NotifService 1.0 NotifService.qml
NotifItem 1.0 NotifItem.qml NotifItem 1.0 NotifItem.qml
NotifPopup 1.0 NotifPopup.qml NotifPopup 1.0 NotifPopup.qml