355 lines
12 KiB
QML
355 lines
12 KiB
QML
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Io
|
|
import Quickshell.Hyprland
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
|
|
import Quickshell.Services.SystemTray
|
|
import Quickshell.DBusMenu
|
|
import Quickshell.Widgets
|
|
|
|
Scope {
|
|
id: root
|
|
property string time
|
|
property int cpuUsage: 0
|
|
property string memUsage: "0"
|
|
property var lastCpuIdle: 0
|
|
property var lastCpuTotal: 0
|
|
|
|
SystemClock {
|
|
id: sysClock
|
|
precision: SystemClock.Seconds
|
|
}
|
|
|
|
Process {
|
|
id: cpuProc
|
|
command: ["sh", "-c", "head -1 /proc/stat"]
|
|
stdout: SplitParser {
|
|
onRead: data => {
|
|
if (!data)
|
|
return;
|
|
var p = data.trim().split(/\s+/);
|
|
var idle = parseInt(p[4]) + parseInt(p[5]);
|
|
var total = p.slice(1, 8).reduce((a, b) => a + parseInt(b), 0);
|
|
if (lastCpuTotal > 0) {
|
|
cpuUsage = Math.round(100 * (1 - (idle - lastCpuIdle) / (total - lastCpuTotal)));
|
|
}
|
|
lastCpuTotal = total;
|
|
lastCpuIdle = idle;
|
|
}
|
|
}
|
|
Component.onCompleted: running = true
|
|
}
|
|
|
|
// Read raw millidegrees value from sysfs (adjust hwmon index if needed)
|
|
// command: ["cat", "/sys/class/hwmon/hwmon0/temp1_input"]
|
|
|
|
Process {
|
|
id: memProc
|
|
command: ["sh", "-c", "free -m | grep Mem"]
|
|
stdout: SplitParser {
|
|
onRead: data => {
|
|
if (!data)
|
|
return;
|
|
var parts = data.trim().split(/\s+/);
|
|
var total = (parts[1] / 1000).toFixed(2) || 1;
|
|
var used = (parts[2] / 1000).toFixed(2) || 0;
|
|
memUsage = `${used} / ${total}`;
|
|
}
|
|
}
|
|
Component.onCompleted: running = true
|
|
}
|
|
|
|
Timer {
|
|
interval: 1000
|
|
running: true
|
|
repeat: true
|
|
onTriggered: {
|
|
cpuProc.running = true;
|
|
memProc.running = true;
|
|
}
|
|
}
|
|
|
|
Variants {
|
|
model: Quickshell.screens
|
|
|
|
PanelWindow {
|
|
id: barRoot
|
|
required property var modelData
|
|
screen: modelData
|
|
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(screen)
|
|
|
|
// Theme
|
|
property color colBg: Qt.rgba(0.0, 0.0, 0.0, 0.35)// "#150d16"
|
|
property color colFg: "#f5dbc4"
|
|
property color colMuted: "#444b6a"
|
|
property color colCyan: "#0db9d7"
|
|
property color colBlue: "#7aa2f7"
|
|
property color colYellow: "#e0af68"
|
|
property color colWhite: "#FFFFFF"
|
|
property color colRed: "#FF2400"
|
|
property string fontFamily: "Inter"
|
|
property int fontSize: 16
|
|
|
|
anchors.bottom: true
|
|
anchors.left: true
|
|
anchors.right: true
|
|
implicitHeight: 35
|
|
color: Qt.rgba(0.0, 0.0, 0.0, 0.35)
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 8
|
|
spacing: 8
|
|
|
|
// Workspaces
|
|
Repeater {
|
|
model: Hyprland.workspaces
|
|
|
|
delegate: Loader {
|
|
active: modelData.monitor && modelData.monitor.name === monitor.name && modelData.id != -98
|
|
|
|
sourceComponent: Text {
|
|
property bool isActive: Hyprland.focusedWorkspace?.id === modelData.id
|
|
text: modelData.id
|
|
|
|
color: isActive ? barRoot.colRed : barRoot.colFg
|
|
font {
|
|
family: barRoot.fontFamily
|
|
pixelSize: barRoot.fontSize
|
|
bold: true
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: ws.activate()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
id: appIconRow
|
|
spacing: 4
|
|
|
|
property var sortedTopLevels: {
|
|
const tl = barRoot.monitor?.activeWorkspace?.toplevels;
|
|
if (!tl)
|
|
return [];
|
|
return tl.values.slice().sort((a, b) => {
|
|
const ax = a.lastIpcObject?.at?.[0] ?? 0;
|
|
const bx = b.lastIpcObject?.at?.[0] ?? 0;
|
|
return ax - bx;
|
|
});
|
|
}
|
|
|
|
Repeater {
|
|
model: appIconRow.sortedTopLevels //barRoot.monitor?.activeWorkspace?.toplevels ?? null
|
|
|
|
delegate: Item {
|
|
id: appIconRoot
|
|
width: 18
|
|
height: 18
|
|
|
|
property var desktopEntry: DesktopEntries.byId(modelData.wayland.appId)
|
|
|
|
IconImage {
|
|
anchors.fill: parent
|
|
source: Quickshell.iconPath(desktopEntry.icon ?? modelData.appId, "image-missing")
|
|
opacity: modelData.activated ? 1.0 : 0.35
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
|
|
onClicked: mouse => {
|
|
if (mouse.button === Qt.LeftButton) {
|
|
modelData.wayland.activate();
|
|
} else if (mouse.button === Qt.RightButton) {
|
|
modelData.wayland.close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: barRoot.monitor?.activeWorkspace?.toplevels ?? null
|
|
function onObjectInsertedPost() {
|
|
Hyprland.refreshToplevels();
|
|
}
|
|
function onObjectRemovedPost() {
|
|
Hyprland.refreshToplevels();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: Hyprland
|
|
function onRawEvent(event) {
|
|
positionRefreshDebounce.restart();
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: positionRefreshDebounce
|
|
interval: 100
|
|
onTriggered: Hyprland.refreshToplevels()
|
|
}
|
|
}
|
|
|
|
Text {
|
|
id: clock
|
|
anchors.centerIn: parent
|
|
property var timeFmt: "h:mm:ss AP"
|
|
property var timeStr: Qt.formatDateTime(sysClock.date, timeFmt)
|
|
|
|
color: barRoot.colFg
|
|
font {
|
|
family: barRoot.fontFamily
|
|
pixelSize: barRoot.fontSize
|
|
bold: true
|
|
}
|
|
text: timeStr
|
|
|
|
Calendar {
|
|
visible: clockArea.containsMouse
|
|
anchorItem: clock
|
|
today: sysClock.date
|
|
bgColor: barRoot.colBg
|
|
fgColor: barRoot.colFg
|
|
mutedColor: barRoot.colMuted
|
|
accentColor: barRoot.colRed
|
|
fontFamily: barRoot.fontFamily
|
|
fontSize: barRoot.fontSize
|
|
}
|
|
|
|
MouseArea {
|
|
id: clockArea
|
|
hoverEnabled: true
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
clock.timeFmt = (clock.timeFmt === "h:mm:ss AP") ? "dddd M/d/yyyy - h:mm:ss AP" : "h:mm:ss AP";
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
IconImage {
|
|
implicitSize: 20
|
|
source: Quickshell.iconPath("device_cpu")
|
|
}
|
|
|
|
// CPU
|
|
Text {
|
|
text: `${cpuUsage}%`
|
|
color: barRoot.colFg
|
|
font {
|
|
family: barRoot.fontFamily
|
|
pixelSize: barRoot.fontSize
|
|
bold: true
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: 1
|
|
height: 16
|
|
color: barRoot.colMuted
|
|
}
|
|
|
|
IconImage {
|
|
implicitSize: 20
|
|
source: Quickshell.iconPath("memory")
|
|
}
|
|
|
|
// Memory
|
|
Text {
|
|
text: `${memUsage} GB`
|
|
color: barRoot.colFg
|
|
font {
|
|
family: barRoot.fontFamily
|
|
pixelSize: barRoot.fontSize
|
|
bold: true
|
|
}
|
|
}
|
|
|
|
Row {
|
|
spacing: 5
|
|
Repeater {
|
|
model: SystemTray.items
|
|
delegate: Image {
|
|
id: trayIcon
|
|
source: modelData.icon
|
|
|
|
width: 16
|
|
height: 16
|
|
|
|
QsMenuOpener {
|
|
id: menuOpener
|
|
menu: modelData.menu
|
|
}
|
|
|
|
PopupWindow {
|
|
id: tooltip
|
|
visible: trayDelegate.containsMouse
|
|
color: "transparent"
|
|
implicitWidth: tooltipText.implicitWidth + 16
|
|
implicitHeight: tooltipText.implicitHeight + 10
|
|
|
|
anchor.item: trayIcon
|
|
anchor.edges: Edges.Top | Edges.Left
|
|
anchor.gravity: Edges.Top | Edges.Right
|
|
anchor.adjustment: PopupAdjustment.SlideX | PopupAdjustment.FlipY
|
|
anchor.margins.bottom: 6
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: barRoot.colBg
|
|
radius: 4
|
|
border.color: barRoot.colMuted
|
|
border.width: 1
|
|
|
|
Text {
|
|
id: tooltipText
|
|
anchors.centerIn: parent
|
|
text: modelData.tooltipTitle || modelData.id
|
|
color: barRoot.colFg
|
|
font {
|
|
family: barRoot.fontFamily
|
|
pixelSize: barRoot.fontSize - 2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: trayDelegate
|
|
hoverEnabled: true
|
|
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
onClicked: mouse => {
|
|
if (mouse.button === Qt.LeftButton) {
|
|
modelData.activate();
|
|
} else if (mouse.button === Qt.RightButton && modelData.hasMenu) {
|
|
let pos = trayIcon.mapToItem(null, 0, trayIcon.height);
|
|
modelData.display(barRoot, pos.x, pos.y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BackgroundEffect.blurRegion: Region {
|
|
item: barRoot.contentItem
|
|
}
|
|
}
|
|
}
|
|
}
|