Files

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
}
}
}
}