Files
dotfiles/quickshell/shell.qml
2026-05-18 08:19:40 +02:00

248 lines
8.8 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 15, 1 = secondary → ws 610)
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 }
}
}
}
}
}
}