import Quickshell._Window import Quickshell.I3 import Quickshell.Services.UPower import Quickshell.Services.Pipewire import Quickshell.Services.SystemTray import Quickshell import Quickshell.Widgets import QtQuick import QtQuick.Layouts import QtQuick.Controls as QQC2 FloatingWindow { id: root title: "Quickshell Bar" visible: true implicitWidth: 1920 implicitHeight: 40 property string mode: "" property string windowTitle: "" Component.onCompleted: { I3.rawEvent.connect(handleRawEvent) pollWindowTitle() pinBar() } function pinBar() { I3.dispatch('[title="Quickshell Bar"] floating enable') I3.dispatch('[title="Quickshell Bar"] move absolute position 0 0') I3.dispatch('[title="Quickshell Bar"] resize set 1920 40') I3.dispatch('[title="Quickshell Bar"] sticky enable') } function handleRawEvent(event) { if (event.type === "mode") { root.mode = event.data === "default" ? "" : event.data } if (event.type === "window" || event.type === "workspace") { pollWindowTitle() } } function pollWindowTitle() { try { var ws = I3.focusedWorkspace if (ws && ws.lastIpcObject) { root.windowTitle = findFocusedTitle(ws.lastIpcObject.nodes || []) } } catch (e) { root.windowTitle = "" } } function findFocusedTitle(nodes) { for (var i = 0; i < nodes.length; i++) { var n = nodes[i] if (n.focused && n.name && !["$external", "i3bar"].includes(n.name)) { return n.name } if (n.nodes && findFocusedTitle(n.nodes)) return findFocusedTitle(n.nodes) if (n.floating_nodes) { for (var j = 0; j < n.floating_nodes.length; j++) { var fn = n.floating_nodes[j] if (fn.focused && fn.name && fn.name !== "$external") return fn.name } } } return "" } function wsColor(num) { var colors = ["#eb4d4b", "#64727D", "#f7ca18", "#fb6f92", "#2ecc71", "#2980b9", "#f7768e", "#667581"] return colors[num % 8] || colors[0] } function updateBattery() { if (!UPower.displayDevice || !UPower.displayDevice.isReady || !UPower.displayDevice.powerSupply) { batteryLabel.text = "" return } var dev = UPower.displayDevice var pct = Math.round(dev.percentage) var state = dev.state batteryItem.isCritical = pct <= 15 && state === 2 if (state === 4) { batteryLabel.text = pct + "%"; return } var timeStr = "" var seconds = 0 if (state === 1 || state === 5) seconds = dev.timeToFull * 60 else if (state === 2 || state === 6) seconds = dev.timeToEmpty * 60 if (seconds > 0 && seconds < 86400) { var h = Math.floor(seconds / 3600) var m = Math.floor((seconds % 3600) / 60) timeStr = h > 0 ? h + ":" + m.toString().padStart(2, '0') + " " : m + "m " } var suffix = "" if (state === 1 || state === 5) suffix = " C" else if (state === 3) suffix = " P" batteryLabel.text = timeStr + pct + "%" + suffix } // Background Rectangle { anchors.fill: parent color: "black" z: 0 } // Bar content RowLayout { anchors.fill: parent anchors.leftMargin: 4 anchors.rightMargin: 4 anchors.top: parent.top anchors.bottom: parent.bottom spacing: 2 z: 1 // LEFT SECTION Item { Layout.fillWidth: true Layout.maximumWidth: root.implicitWidth * 0.33 RowLayout { anchors.fill: parent spacing: 2 // Workspaces RowLayout { Layout.fillWidth: true Layout.maximumWidth: 600 spacing: 2 Repeater { model: I3.workspaces delegate: QQC2.ToolButton { id: wsBtn property bool isFocused: modelData.focused property int wsNum: modelData.number text: modelData.name visible: modelData.active || modelData.focused || modelData.urgent highlighted: isFocused checkable: true checked: isFocused down: isFocused onClicked: { if (modelData.activate) modelData.activate() else I3.dispatch(`workspace ${modelData.name}`) } font.pixelSize: 14 font.bold: true width: implicitWidth + 8 padding: 0 palette { button: isFocused ? wsColor(wsNum) : "transparent" buttonText: "#ffffff" } // Hover underline background: Rectangle { anchors.fill: parent color: "transparent" Rectangle { anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right height: 3 color: wsBtn.hovered || wsBtn.isFocused ? "#ffffff" : "transparent" } } } } } // Mode indicator QQC2.Label { text: root.mode visible: root.mode !== "" color: "#64727D" font.pixelSize: 14 font.bold: true font.italic: true padding: 10 } // Window title QQC2.Label { text: root.windowTitle visible: root.windowTitle !== "" Layout.fillWidth: true Layout.maximumWidth: 300 elide: Text.ElideMiddle color: "#ffffff" font.pixelSize: 14 font.bold: true padding: 10 } } } // Spacer Item { Layout.fillWidth: true } // RIGHT SECTION RowLayout { spacing: 2 // Volume Item { height: parent.height QQC2.Label { id: volumeLabel anchors.fill: parent anchors.leftMargin: 10 anchors.rightMargin: 10 anchors.verticalCenter: parent.verticalCenter color: "#ffffff" font.pixelSize: 14 font.bold: true horizontalAlignment: Text.AlignHCenter text: { if (!Pipewire.ready || !Pipewire.defaultAudioSink) return "" var sink = Pipewire.defaultAudioSink var vol = sink.audio ? sink.audio.averageVolume * 100 : 0 var muted = sink.audio ? sink.audio.isMuted : false return muted ? "Muted" : Math.round(vol) + "%" } MouseArea { anchors.fill: parent onClicked: Qt.shellCommand("pavucontrol") onWheel: function(event) { if (Pipewire.ready && Pipewire.defaultAudioSink && Pipewire.defaultAudioSink.audio) { var v = Pipewire.defaultAudioSink.audio.averageVolume var step = event.angleDelta.y > 0 ? 0.05 : -0.05 Pipewire.defaultAudioSink.audio.averageVolume = Math.max(0, Math.min(1, v + step)) } } } } } // Battery Item { id: batteryItem height: parent.height property bool isCritical: false QQC2.Label { id: batteryLabel anchors.fill: parent anchors.leftMargin: 10 anchors.rightMargin: 10 anchors.verticalCenter: parent.verticalCenter color: batteryItem.isCritical ? "#f53c3c" : "#ffffff" font.pixelSize: 14 font.bold: true horizontalAlignment: Text.AlignHCenter } Timer { interval: 5000 running: true repeat: true onTriggered: root.updateBattery() } Component.onCompleted: root.updateBattery() } // Clock Item { height: parent.height QQC2.Label { id: clockLabel anchors.fill: parent anchors.leftMargin: 10 anchors.rightMargin: 10 anchors.verticalCenter: parent.verticalCenter color: "#ffffff" font.pixelSize: 14 font.bold: true horizontalAlignment: Text.AlignHCenter } Timer { interval: 1000 running: true repeat: true onTriggered: { var now = new Date() var h = now.getHours() var ampm = h >= 12 ? "PM" : "AM" h = h % 12 || 12 var time = h + ":" + now.getMinutes().toString().padStart(2, '0') + ":" + now.getSeconds().toString().padStart(2, '0') + " " + ampm var date = now.getFullYear() + "-" + (now.getMonth()+1).toString().padStart(2, '0') + "-" + now.getDate().toString().padStart(2, '0') clockLabel.text = time + " " + date } } } // System Tray Item { id: trayContainer height: parent.height Repeater { model: SystemTray.items delegate: Item { width: 24 height: trayContainer.height IconImage { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 2 width: 22 height: 22 source: modelData.iconName || "" visible: source !== "" MouseArea { anchors.fill: parent onClicked: function(mouse) { if (modelData.hasMenu) { modelData.display(QsWindowAttached.window, 0, 0) } else { modelData.activate() } } } } } } } } } }