464 lines
17 KiB
QML
464 lines
17 KiB
QML
import Quickshell
|
|
import Quickshell.Hyprland
|
|
import Quickshell.Widgets
|
|
import Quickshell.Services.SystemTray
|
|
import Quickshell.Io
|
|
import QtQuick
|
|
|
|
PanelWindow {
|
|
id: bar
|
|
|
|
required property var walColors
|
|
required property string motd
|
|
required property int notificationCount
|
|
required property var latestNotification
|
|
property var notifServer: null
|
|
required property SystemStats stats
|
|
required property string fontFamily
|
|
|
|
// --- Notification toast state ---
|
|
property int _toastCounter: 0
|
|
|
|
onLatestNotificationChanged: {
|
|
if (latestNotification) {
|
|
toastModel.append({
|
|
toastId: _toastCounter++,
|
|
appName: latestNotification.appName || "",
|
|
summary: latestNotification.summary || "",
|
|
body: latestNotification.body || ""
|
|
})
|
|
}
|
|
}
|
|
|
|
ListModel {
|
|
id: toastModel
|
|
}
|
|
|
|
anchors {
|
|
top: true
|
|
left: true
|
|
right: true
|
|
}
|
|
|
|
color: "transparent"
|
|
exclusionMode: ExclusionMode.Normal
|
|
exclusiveZone: 34
|
|
implicitHeight: 34
|
|
|
|
// --- Theme colors ---
|
|
property color bgColor: (walColors.special && walColors.special.background) ? walColors.special.background : "#1d160d"
|
|
property color fgColor: (walColors.special && walColors.special.foreground) ? walColors.special.foreground : "#d8d8d3"
|
|
property color accentColor: (walColors.colors && walColors.colors.color1) ? walColors.colors.color1 : "#946F50"
|
|
property color accent2Color: (walColors.colors && walColors.colors.color2) ? walColors.colors.color2 : "#BA9351"
|
|
property color accent3Color: (walColors.colors && walColors.colors.color3) ? walColors.colors.color3 : "#BA9351"
|
|
property color accent4Color: (walColors.colors && walColors.colors.color4) ? walColors.colors.color4 : "#BA9351"
|
|
property color accent5Color: (walColors.colors && walColors.colors.color5) ? walColors.colors.color5 : "#BA9351"
|
|
property color accent6Color: (walColors.colors && walColors.colors.color6) ? walColors.colors.color6 : "#BA9351"
|
|
property color accent7Color: (walColors.colors && walColors.colors.color7) ? walColors.colors.color7 : "#BA9351"
|
|
|
|
// --- Hyprland state ---
|
|
property var hyprMonitor: Hyprland.monitorFor(screen)
|
|
|
|
property var monitorWorkspaces: {
|
|
let vals = Hyprland.workspaces.values
|
|
let result = []
|
|
for (let i = 0; i < vals.length; i++) {
|
|
if (vals[i].monitor === hyprMonitor && vals[i].id > 0)
|
|
result.push(vals[i])
|
|
}
|
|
return result.sort((a, b) => a.id - b.id)
|
|
}
|
|
|
|
property bool specialWorkspaceVisible: false
|
|
|
|
Connections {
|
|
target: Hyprland
|
|
function onRawEvent(event) {
|
|
if (event.name === "activespecial") {
|
|
let parts = event.data.split(",")
|
|
let monName = parts[parts.length - 1]
|
|
if (monName === hyprMonitor?.name) {
|
|
bar.specialWorkspaceVisible = parts[0] !== ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
property string windowTitle: {
|
|
let title = Hyprland.activeToplevel?.title ?? ""
|
|
if (!title || title === "~" || title === "kitty" || title === "fish")
|
|
return motd
|
|
return title
|
|
}
|
|
|
|
// --- Clock ---
|
|
SystemClock {
|
|
id: clock
|
|
precision: SystemClock.Minutes
|
|
}
|
|
|
|
// --- Bar layout ---
|
|
Item {
|
|
anchors.fill: parent
|
|
anchors.margins: 2
|
|
|
|
// Left module group
|
|
Rectangle {
|
|
id: leftGroup
|
|
z: 1
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
height: 30
|
|
width: leftContent.width + 8
|
|
clip: true
|
|
color: bar.bgColor
|
|
border.color: bar.accentColor
|
|
border.width: 2
|
|
radius: 100
|
|
|
|
Behavior on width {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
|
}
|
|
|
|
Row {
|
|
id: leftContent
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 4
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 0
|
|
|
|
Workspaces {
|
|
workspaces: bar.monitorWorkspaces
|
|
accentColor: bar.accentColor
|
|
fgColor: bar.fgColor
|
|
fontFamily: bar.fontFamily
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
// Special workspace badge
|
|
Rectangle {
|
|
id: specialBadge
|
|
z: 0
|
|
anchors.verticalCenter: leftGroup.verticalCenter
|
|
height: 28
|
|
width: specialText.implicitWidth + (bar.specialWorkspaceVisible ? 30 : 10)
|
|
topLeftRadius: 0
|
|
bottomLeftRadius: 0
|
|
topRightRadius: 100
|
|
bottomRightRadius: 100
|
|
color: bar.bgColor
|
|
border.color: bar.accentColor
|
|
border.width: 2
|
|
visible: bar.specialWorkspaceVisible
|
|
|
|
x: bar.specialWorkspaceVisible ? leftGroup.x + leftGroup.width - 10 : leftGroup.x + leftGroup.width - specialBadge.width
|
|
|
|
Behavior on x {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutCubic }
|
|
}
|
|
|
|
Behavior on width {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutCubic }
|
|
}
|
|
Text {
|
|
id: specialText
|
|
anchors.centerIn: parent
|
|
anchors.horizontalCenterOffset: 4
|
|
text: "\uf2d2"
|
|
color: bar.fgColor
|
|
font.pixelSize: 11
|
|
font.family: bar.fontFamily
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: Hyprland.dispatch("togglespecialworkspace magic")
|
|
}
|
|
}
|
|
|
|
// Center module group
|
|
Rectangle {
|
|
id: centerGroup
|
|
anchors.centerIn: parent
|
|
height: 30
|
|
width: centerContent.width + 16
|
|
clip: true
|
|
color: bar.bgColor
|
|
border.color: bar.accentColor
|
|
border.width: 2
|
|
radius: 100
|
|
|
|
Behavior on width {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
|
}
|
|
|
|
Row {
|
|
id: centerContent
|
|
anchors.centerIn: parent
|
|
spacing: 12
|
|
|
|
Text {
|
|
text: bar.windowTitle
|
|
color: bar.fgColor
|
|
font.pixelSize: 13
|
|
font.family: bar.fontFamily
|
|
elide: Text.ElideRight
|
|
width: Math.min(implicitWidth, 900)
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notification toasts
|
|
Repeater {
|
|
model: toastModel
|
|
|
|
delegate: NotificationToast {
|
|
bgColor: bar.bgColor
|
|
fgColor: bar.fgColor
|
|
accentColor: bar.accentColor
|
|
fontFamily: bar.fontFamily
|
|
rightAnchor: rightGroup
|
|
sourceModel: toastModel
|
|
}
|
|
}
|
|
|
|
// Right module group
|
|
Rectangle {
|
|
id: rightGroup
|
|
z: 1
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
height: 30
|
|
width: rightContent.width + 8
|
|
clip: true
|
|
color: bar.bgColor
|
|
border.color: bar.accentColor
|
|
border.width: 2
|
|
radius: 100
|
|
|
|
Behavior on x {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
|
}
|
|
Behavior on width {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
|
}
|
|
|
|
Row {
|
|
id: rightContent
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 4
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 5
|
|
|
|
// Notification count
|
|
Item {
|
|
id: notifBadgeWrapper
|
|
property real targetWidth: bar.notificationCount > 0 ? notifCountText.implicitWidth + 16 : 0
|
|
height: 22
|
|
width: targetWidth
|
|
clip: true
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: width > 0
|
|
|
|
Behavior on width {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.left: parent.left
|
|
height: 22
|
|
width: notifCountText.implicitWidth + 16
|
|
topLeftRadius: 100
|
|
bottomLeftRadius: 100
|
|
radius: parent.visible ? 4 : 100
|
|
color: bar.accent7Color
|
|
|
|
Behavior on radius {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
|
}
|
|
Text {
|
|
id: notifCountText
|
|
anchors.centerIn: parent
|
|
text: bar.notificationCount + " \uf0f3"
|
|
color: bar.bgColor
|
|
font.pixelSize: 13
|
|
font.family: bar.fontFamily
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
if (!bar.notifServer) return
|
|
let notifs = bar.notifServer.trackedNotifications.values
|
|
for (let i = notifs.length - 1; i >= 0; i--)
|
|
notifs[i].dismiss()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Updates
|
|
BarPill {
|
|
id: updatesPill
|
|
fontFamily: bar.fontFamily
|
|
label: stats.updatesCount + (stats.updatesCount === 0 ? " \udb80\udca2" : " \udb84\udf77")
|
|
pillColor: bar.accent6Color
|
|
textColor: bar.bgColor
|
|
tooltipText: stats.updatesList || "Up to date"
|
|
topLeftRadius: bar.notificationCount > 0 ? 4 : 100
|
|
bottomLeftRadius: bar.notificationCount > 0 ? 4 : 100
|
|
|
|
Behavior on topLeftRadius {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
|
}
|
|
Behavior on bottomLeftRadius {
|
|
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: mouse => {
|
|
if (mouse.button === Qt.RightButton)
|
|
stats.checkUpdates()
|
|
else
|
|
updateProc.running = true
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: updateProc
|
|
command: ["kitty", "--title", "System Update", "-e", "bash", "-c", "yay && echo 'Update completed successfully!' || { echo 'Update failed! Press any key to close...'; read -n 1; }"]
|
|
}
|
|
}
|
|
|
|
// CPU
|
|
BarPill {
|
|
fontFamily: bar.fontFamily
|
|
label: stats.cpuUsage + "% \uf2db"
|
|
pillColor: bar.accent5Color
|
|
textColor: bar.bgColor
|
|
}
|
|
|
|
// Memory
|
|
BarPill {
|
|
id: memPill
|
|
fontFamily: bar.fontFamily
|
|
label: stats.memUsage + "% \uf0c9"
|
|
pillColor: bar.accent4Color
|
|
textColor: bar.bgColor
|
|
tooltipText: stats.memInfo
|
|
}
|
|
|
|
// Temperature
|
|
BarPill {
|
|
id: tempPill
|
|
fontFamily: bar.fontFamily
|
|
label: {
|
|
let t = stats.temperature
|
|
let icon = t >= 80 ? "\uf2c7" : "\uf2c9"
|
|
return t + "\u00b0C " + icon
|
|
}
|
|
pillColor: bar.accent3Color
|
|
textColor: bar.bgColor
|
|
tooltipText: stats.cpuTemps || "No sensors"
|
|
}
|
|
|
|
// Clock
|
|
BarPill {
|
|
id: clockPill
|
|
fontFamily: bar.fontFamily
|
|
label: Qt.formatDateTime(clock.date, "hh:mm")
|
|
pillColor: bar.accent2Color
|
|
textColor: bar.bgColor
|
|
tooltipText: Qt.formatDateTime(clock.date, "dddd, MMMM d, yyyy")
|
|
}
|
|
|
|
// System tray
|
|
Rectangle {
|
|
height: 22
|
|
width: trayRow.width + 16
|
|
topLeftRadius: 4
|
|
bottomLeftRadius: 4
|
|
topRightRadius: 100
|
|
bottomRightRadius: 100
|
|
color: bar.accentColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Row {
|
|
id: trayRow
|
|
anchors.centerIn: parent
|
|
spacing: 6
|
|
|
|
Repeater {
|
|
model: SystemTray.items
|
|
|
|
delegate: Item {
|
|
id: trayDelegate
|
|
required property var modelData
|
|
|
|
width: 16
|
|
height: 16
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Image {
|
|
anchors.fill: parent
|
|
source: modelData.icon
|
|
sourceSize.width: 16
|
|
sourceSize.height: 16
|
|
fillMode: Image.PreserveAspectFit
|
|
}
|
|
|
|
QsMenuAnchor {
|
|
id: menuAnchor
|
|
menu: modelData.menu
|
|
anchor.window: bar
|
|
anchor.rect.x: trayDelegate.mapToItem(bar.contentItem, 0, 0).x
|
|
anchor.rect.y: trayDelegate.mapToItem(bar.contentItem, 0, 0).y
|
|
anchor.rect.width: trayDelegate.width
|
|
anchor.rect.height: trayDelegate.height
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
onClicked: mouse => {
|
|
if (mouse.button === Qt.RightButton)
|
|
menuAnchor.open()
|
|
else
|
|
modelData.activate()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Tooltips ---
|
|
BarTooltip {
|
|
bgColor: bar.bgColor; fgColor: bar.fgColor; borderColor: bar.accentColor; fontFamily: bar.fontFamily
|
|
target: updatesPill; text: updatesPill.tooltipText
|
|
}
|
|
BarTooltip {
|
|
bgColor: bar.bgColor; fgColor: bar.fgColor; borderColor: bar.accentColor; fontFamily: bar.fontFamily
|
|
target: memPill; text: memPill.tooltipText
|
|
}
|
|
BarTooltip {
|
|
bgColor: bar.bgColor; fgColor: bar.fgColor; borderColor: bar.accentColor; fontFamily: bar.fontFamily
|
|
target: tempPill; text: tempPill.tooltipText
|
|
}
|
|
BarTooltip {
|
|
bgColor: bar.bgColor; fgColor: bar.fgColor; borderColor: bar.accentColor; fontFamily: bar.fontFamily
|
|
target: clockPill; text: clockPill.tooltipText
|
|
}
|
|
}
|