248 lines
8.8 KiB
QML
248 lines
8.8 KiB
QML
import Quickshell
|
||
import Quickshell.Io
|
||
import Quickshell.Wayland
|
||
import Quickshell.Hyprland
|
||
import QtQuick
|
||
import QtQuick.Layouts
|
||
|
||
// ─────────────────────────────────────────────
|
||
// Shell root – spawns one bar per screen
|
||
// ─────────────────────────────────────────────
|
||
ShellRoot {
|
||
id: root
|
||
|
||
// ── Global polled data ───────────────────
|
||
property string clockTime: ""
|
||
property string clockDate: ""
|
||
property string cpuUsage: ""
|
||
property string memUsed: ""
|
||
property string networkInfo: ""
|
||
property string volumeInfo: ""
|
||
property string themeInfo: ""
|
||
|
||
// Clock – every second
|
||
Process {
|
||
id: clockTimeProc
|
||
command: ["date", "+%H:%M:%S"]
|
||
running: true
|
||
stdout: StdioCollector { onStreamFinished: clockTime = this.text.trim() }
|
||
}
|
||
Timer {
|
||
interval: 1000; running: true; repeat: true
|
||
onTriggered: clockTimeProc.running = true
|
||
}
|
||
|
||
Process {
|
||
id: clockDateProc
|
||
command: ["date", "+%A, %B %d %Y"]
|
||
running: true
|
||
stdout: StdioCollector { onStreamFinished: clockDate = this.text.trim() }
|
||
}
|
||
Timer {
|
||
interval: 1000; running: true; repeat: true
|
||
onTriggered: clockDateProc.running = true
|
||
}
|
||
|
||
// CPU – every 2 s
|
||
Process {
|
||
id: cpuProc
|
||
command: ["bash", "-c", "top -bn1 | grep 'Cpu(s)' | awk '{print int($2+$4)}'"]
|
||
running: true
|
||
stdout: StdioCollector { onStreamFinished: cpuUsage = this.text.trim() }
|
||
}
|
||
Timer {
|
||
interval: 2000; running: true; repeat: true
|
||
onTriggered: cpuProc.running = true
|
||
}
|
||
|
||
// Memory – every 2 s
|
||
Process {
|
||
id: memProc
|
||
command: ["bash", "-c", "free -g --si | awk '/Mem:/{printf \"%.1f\", $3}'"]
|
||
running: true
|
||
stdout: StdioCollector { onStreamFinished: memUsed = this.text.trim() }
|
||
}
|
||
Timer {
|
||
interval: 2000; running: true; repeat: true
|
||
onTriggered: memProc.running = true
|
||
}
|
||
|
||
// Network – every 5 s
|
||
Process {
|
||
id: netProc
|
||
command: ["bash", Qt.resolvedUrl("scripts/get-network.sh")]
|
||
running: true
|
||
stdout: StdioCollector { onStreamFinished: networkInfo = this.text.trim() }
|
||
}
|
||
Timer {
|
||
interval: 5000; running: true; repeat: true
|
||
onTriggered: netProc.running = true
|
||
}
|
||
|
||
// Volume – every 1 s
|
||
Process {
|
||
id: volProc
|
||
command: ["bash", Qt.resolvedUrl("scripts/get-volume.sh")]
|
||
running: true
|
||
stdout: StdioCollector { onStreamFinished: volumeInfo = this.text.trim() }
|
||
}
|
||
Timer {
|
||
interval: 1000; running: true; repeat: true
|
||
onTriggered: volProc.running = true
|
||
}
|
||
|
||
// Theme – every 2 s
|
||
Process {
|
||
id: themeProc
|
||
command: ["bash", Qt.resolvedUrl("scripts/get-theme.sh")]
|
||
running: true
|
||
stdout: StdioCollector { onStreamFinished: themeInfo = this.text.trim() }
|
||
}
|
||
Timer {
|
||
interval: 2000; running: true; repeat: true
|
||
onTriggered: themeProc.running = true
|
||
}
|
||
|
||
// ── One bar per screen ───────────────────
|
||
Variants {
|
||
model: Quickshell.screens
|
||
|
||
|
||
PanelWindow {
|
||
id: bar
|
||
required property var modelData
|
||
screen: modelData
|
||
|
||
color: '#1c1d20'
|
||
|
||
anchors { top: true; left: true; right: true }
|
||
implicitHeight: 30
|
||
exclusiveZone: implicitHeight // reserve space (replaces :exclusive true)
|
||
|
||
|
||
// Which screen slot is this? (0 = primary → ws 1–5, 1 = secondary → ws 6–10)
|
||
property int screenIndex: {
|
||
var screens = Quickshell.screens
|
||
for (var i = 0; i < screens.length; i++) {
|
||
if (screens[i] === modelData) return i
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// The 5 workspace IDs that belong to this bar
|
||
readonly property int wsFirst: screenIndex === 0 ? 1 : 6
|
||
readonly property int wsLast: screenIndex === 0 ? 5 : 10
|
||
|
||
// ── Bar layout (centerbox equivalent) ──
|
||
RowLayout {
|
||
anchors.fill: parent
|
||
spacing: 0
|
||
anchors.leftMargin: 8
|
||
anchors.rightMargin: 8
|
||
|
||
// LEFT – workspaces (native Hyprland model, filtered per screen)
|
||
Item {
|
||
Layout.fillWidth: true
|
||
Layout.fillHeight: true
|
||
|
||
Row {
|
||
anchors { left: parent.left; verticalCenter: parent.verticalCenter }
|
||
spacing: 4
|
||
|
||
Repeater {
|
||
// Always show slots wsFirst..wsLast, even if Hyprland
|
||
// hasn't created the workspace yet (shows as empty).
|
||
model: bar.wsLast - bar.wsFirst + 1
|
||
|
||
delegate: Rectangle {
|
||
required property int index
|
||
readonly property int wsId: bar.wsFirst + index
|
||
|
||
// Look up live workspace from Hyprland
|
||
readonly property HyprlandWorkspace liveWs: {
|
||
for (var i = 0; i < Hyprland.workspaces.values.length; i++) {
|
||
if (Hyprland.workspaces.values[i].id === wsId)
|
||
return Hyprland.workspaces.values[i]
|
||
}
|
||
return null
|
||
}
|
||
|
||
readonly property bool active: Hyprland.focusedMonitor !== null
|
||
&& Hyprland.focusedMonitor.activeWorkspace !== null
|
||
&& Hyprland.focusedMonitor.activeWorkspace.id === wsId
|
||
&& Hyprland.focusedMonitor.name === bar.modelData.name
|
||
|
||
readonly property bool occupied: liveWs !== null
|
||
&& liveWs.windowCount > 0
|
||
|
||
implicitWidth: 22
|
||
implicitHeight: 22
|
||
radius: 4
|
||
|
||
color: active ? "#89b4fa" // focused
|
||
: occupied ? "#313244" // has windows
|
||
: "transparent" // empty
|
||
|
||
border.color: active ? "transparent" : "#585b70"
|
||
border.width: active ? 0 : 1
|
||
|
||
Text {
|
||
anchors.centerIn: parent
|
||
text: wsId
|
||
color: active ? "#1e1e2e" : "#cdd6f4"
|
||
font.pixelSize: 11
|
||
font.family: "monospace"
|
||
}
|
||
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
cursorShape: Qt.PointingHandCursor
|
||
onClicked: Hyprland.dispatch("workspace " + wsId)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// CENTER – clock
|
||
Item {
|
||
Layout.fillWidth: true
|
||
Layout.fillHeight: true
|
||
|
||
ClockWidget {
|
||
anchors.centerIn: parent
|
||
clockTime: root.clockTime
|
||
clockDate: root.clockDate
|
||
}
|
||
}
|
||
|
||
// RIGHT – stats + menus
|
||
Item {
|
||
Layout.fillWidth: true
|
||
Layout.fillHeight: true
|
||
|
||
RowLayout {
|
||
anchors { right: parent.right; verticalCenter: parent.verticalCenter }
|
||
spacing: 2
|
||
|
||
// Primary screen (0) gets full menus; secondary gets stats only
|
||
SettingsMenu {
|
||
visible: bar.screenIndex === 0
|
||
volumeInfo: root.volumeInfo
|
||
networkInfo: root.networkInfo
|
||
themeInfo: root.themeInfo
|
||
}
|
||
|
||
LogoutMenu {
|
||
visible: bar.screenIndex === 0
|
||
}
|
||
|
||
CpuWidget { cpuUsage: root.cpuUsage }
|
||
MemWidget { memUsed: root.memUsed }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|