commit 0da7e57a641d49e187febb358d1d4003bf4b89cf Author: Zacharias-Brohn Date: Tue Oct 7 13:53:18 2025 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfbea23 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.qmlls.ini diff --git a/Bar.qml b/Bar.qml new file mode 100644 index 0000000..596d8ad --- /dev/null +++ b/Bar.qml @@ -0,0 +1,86 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import Quickshell.Wayland +import Qt5Compat.GraphicalEffects +import Quickshell.Hyprland +import qs.Modules + +Scope { + Variants { + model: Quickshell.screens + + PanelWindow { + id: bar + required property var modelData + screen: modelData + + anchors { + top: true + left: true + right: true + } + + implicitHeight: 34 + color: "transparent" + + Rectangle { + anchors.fill: parent + color: "#801a1a1a" + radius: 0 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 5 + anchors.rightMargin: 5 + + RowLayout { + id: leftSection + Layout.fillHeight: true + Layout.preferredWidth: 100 + + Workspaces { + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.fillHeight: true + Layout.topMargin: 6 + Layout.bottomMargin: 6 + } + } + + RowLayout { + id: centerSection + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + } + + RowLayout { + id: rightSection + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + + TrayWidget { + id: systemTrayModule + bar: bar + Layout.alignment: Qt.AlignVCenter + } + + Clock { + Layout.alignment: Qt.AlignVCenter + } + + SwayNC { + Layout.alignment: Qt.AlignVCenter + } + } + } + WindowTitle { + anchors.centerIn: parent + width: Math.min( 300, parent.width * 0.4 ) + height: parent.height + z: 1 + } + } + } + } +} diff --git a/Clock.qml b/Clock.qml new file mode 100644 index 0000000..45d4f12 --- /dev/null +++ b/Clock.qml @@ -0,0 +1,6 @@ +import QtQuick + +Text { + text: Time.time + color: "white" +} diff --git a/Modules/ActiveWindow.qml b/Modules/ActiveWindow.qml new file mode 100644 index 0000000..50bb037 --- /dev/null +++ b/Modules/ActiveWindow.qml @@ -0,0 +1,9 @@ +pragma Singleton + +import Quickshell +import Quickshell.Hyprland + +Singleton { + id: root + property string activeWindow: Hyprland.activeToplevel?.lastIpcObject.class || "" +} diff --git a/Modules/CustomTrayMenu.qml b/Modules/CustomTrayMenu.qml new file mode 100644 index 0000000..bd0503c --- /dev/null +++ b/Modules/CustomTrayMenu.qml @@ -0,0 +1,86 @@ +// CustomTrayMenu.qml +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell +import QtQuick.Window // for Window, flags + +PopupWindow { + id: popup + + property QsMenuHandle menuHandle + property alias entries: menuModel + + QsMenuOpener { + id: menu + menu: popup.menuHandle + } + + ListModel { id: menuModel } + + implicitWidth: contentColumn.implicitWidth + 16 + implicitHeight: contentColumn.implicitHeight + 16 + + Rectangle { + color: "#202020CC" + radius: 4 + anchors.fill: parent + } + + Column { + id: contentColumn + anchors.fill: parent + spacing: 4 + Repeater { + id: repeater + model: menuModel + Row { + id: entryRow + height: 30 + width: parent.implicitWidth + property var entry: modelData + MouseArea { + anchors.fill: parent + onClicked: { + if (entryRow.entry.triggered) { + entryRow.entry.triggered() + } + popup.visible = false + } + } + Image { + source: entryRow.entry.icon + width: 20; height: 20 + visible: entryRow.entry.icon !== "" + } + Text { + text: entryRow.entry.text + color: "white" + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + + function rebuild() { + menuModel.clear() + console.log(menu.children.count) + if (!menu) return + for (let i = 0; i < menu.children.count; ++i) { + let e = menu.children.get(i) + menuModel.append({ + text: e.text, + icon: e.icon, + triggered: e.triggered, + entryObject: e + }) + } + } + + onMenuHandleChanged: rebuild + Connections { + target: menu + function onCountChanged() { + popup.rebuild + } + } +} diff --git a/Modules/SwayNC.qml b/Modules/SwayNC.qml new file mode 100644 index 0000000..2d93201 --- /dev/null +++ b/Modules/SwayNC.qml @@ -0,0 +1,94 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +Item { + id: root + implicitWidth: notificationButton.implicitWidth + implicitHeight: notificationButton.implicitHeight + + property var notificationState: ({}) + + function updateState(output) { + try { + notificationState = JSON.parse(output.trim()) + } catch (e) { + console.error("Failed to parse swaync state:", e) + } + } + + function getIcon() { + let count = notificationState["text"] || 0 + let hasNotification = count > 0 + if (hasNotification) return "notification" + return "none" + } + + function getDisplayText() { + let icon = getIcon() + let count = notificationState["count"] || 0 + + if (icon.includes("notification")) { + return "" + } + return "" + } + + Process { + id: swayNcMonitor + running: true + command: ["swaync-client", "-swb"] + + stdout: SplitParser { + onRead: data => root.updateState(data) + } + } + + Process { + id: swayncProcess + command: ["swaync-client", "-t", "-sw"] + running: false + } + + Button { + id: notificationButton + flat: true + + background: Rectangle { + color: "transparent" + radius: 4 + } + + contentItem: RowLayout { + spacing: 0 + + Text { + text: root.getDisplayText() + color: "white" + font.pixelSize: 16 + Layout.alignment: Qt.AlignVCenter + } + + Text { + text: "●" + color: "red" + font.pixelSize: 6 + visible: root.getIcon().includes("notification") + Layout.alignment: Qt.AlignTop | Qt.AlignRight + Layout.topMargin: 0 + Layout.rightMargin: -6 + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + swayncProcess.running = true + } + } + } +} diff --git a/Modules/TrayItem.qml b/Modules/TrayItem.qml new file mode 100644 index 0000000..afb1f02 --- /dev/null +++ b/Modules/TrayItem.qml @@ -0,0 +1,52 @@ +import QtQuick +import Quickshell +import Quickshell.Widgets +import Quickshell.Services.SystemTray +import qs.Modules + +IconImage { + id: root + required property SystemTrayItem item + property var customMenu + + source: root.item.icon + implicitSize: 15 + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: event => { + switch (event.button) { + case Qt.LeftButton: root.item.activate(); break; + case Qt.RightButton: + if (root.item.hasMenu) { + + root.customMenu = menuComponent.createObject(root); + root.customMenu.menuHandle = root.item.menu; + + const window = QsWindow.window; + const widgetRect = window.contentItem.mapFromItem(root, 0, root.height + 4, root.width, root.height); + root.customMenu.anchor.rect = widgetRect + root.customMenu.anchor.window = window + root.customMenu.anchor.adjustment = PopupAdjustment.Flip + root.customMenu.visible = true; + root.customMenu.rebuild(); + // menuAnchor.anchor.rect = widgetRect; + // menuAnchor.open(); + } + break; + } + } + } + + Component { + id: menuComponent + CustomTrayMenu {} + } + + // QsMenuAnchor { + // id: menuAnchor + // menu: root.item.menu + // anchor.window: root.QsWindow.window?? null + // anchor.adjustment: PopupAdjustment.Flip + // } +} diff --git a/Modules/TrayWidget.qml b/Modules/TrayWidget.qml new file mode 100644 index 0000000..e271c67 --- /dev/null +++ b/Modules/TrayWidget.qml @@ -0,0 +1,25 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.SystemTray + +Rectangle { + required property PanelWindow bar + implicitHeight: parent.height + implicitWidth: rowL.implicitWidth + 20 + color: "transparent" + + + RowLayout { + spacing: 10 + id: rowL + anchors.centerIn: parent + Repeater { + model: SystemTray.items + TrayItem { + required property SystemTrayItem modelData + item: modelData + } + } + } +} diff --git a/Modules/WindowTitle.qml b/Modules/WindowTitle.qml new file mode 100644 index 0000000..e3b079d --- /dev/null +++ b/Modules/WindowTitle.qml @@ -0,0 +1,53 @@ +import QtQuick +import QtQuick.Layouts + +Item { + id: root + Layout.fillHeight: true + Layout.preferredWidth: Math.max( titleText1.implicitWidth, titleText2.implicitWidth ) + 10 + clip: true + + property string currentTitle: ActiveWindow.activeWindow + property bool showFirst: true + + onCurrentTitleChanged: { + if (showFirst) { + titleText2.text = currentTitle + showFirst = false + } else { + titleText1.text = currentTitle + showFirst = true + } + } + + Text { + id: titleText1 + anchors.fill: parent + anchors.margins: 5 + text: root.currentTitle + color: "white" + elide: Text.ElideRight + font.pixelSize: 16 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: root.showFirst ? 1 : 0 + Behavior on opacity { + NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } + } + } + + Text { + id: titleText2 + anchors.fill: parent + anchors.margins: 5 + color: "white" + elide: Text.ElideRight + font.pixelSize: 16 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: root.showFirst ? 0 : 1 + Behavior on opacity { + NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } + } + } +} diff --git a/Modules/Workspaces.qml b/Modules/Workspaces.qml new file mode 100644 index 0000000..0993dfc --- /dev/null +++ b/Modules/Workspaces.qml @@ -0,0 +1,84 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland + +Rectangle { + id: root + + implicitWidth: workspacesRow.implicitWidth + 12 + implicitHeight: workspacesRow.implicitHeight + 8 + + color: "#40000000" + radius: height / 2 + + Behavior on implicitWidth { + NumberAnimation { + duration: 100 + easing.type: Easing.InOutQuad + } + } + + RowLayout { + id: workspacesRow + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 6 + spacing: 8 + + Repeater { + model: Hyprland.workspaces + + Rectangle { + required property var modelData + + width: 12 + height: 12 + radius: 6 + + color: modelData.id === Hyprland.focusedWorkspace.id ? "#4080ff" : "#606060" + + border.color: modelData.id === Hyprland.focusedWorkspace.id ? "#60a0ff" : "#808080" + border.width: 1 + + scale: 1.0 + opacity: 1.0 + + Behavior on color { + ColorAnimation { + duration: 150 + easing.type: Easing.InOutQuad + } + } + + Behavior on border.color { + ColorAnimation { + duration: 150 + easing.type: Easing.InOutQuad + } + } + + NumberAnimation on scale { + from: 0.0 + to: 1.0 + duration: 300 + easing.type: Easing.OutBack + } + + NumberAnimation on opacity { + from: 0.0 + to: 1.0 + duration: 200 + } + + MouseArea { + anchors.fill: parent + onClicked: { + Hyprland.dispatch("workspace " + modelData.id) + } + } + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2d91a8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# z-bar-qt diff --git a/Time.qml b/Time.qml new file mode 100644 index 0000000..88670da --- /dev/null +++ b/Time.qml @@ -0,0 +1,17 @@ +// Time.qml +pragma Singleton + +import Quickshell +import QtQuick + +Singleton { + id: root + readonly property string time: { + Qt.formatDateTime(clock.date, "ddd d MMM - hh:mm:ss") + } + + SystemClock { + id: clock + precision: SystemClock.Seconds + } +} diff --git a/shell.qml b/shell.qml new file mode 100644 index 0000000..9c9d2f2 --- /dev/null +++ b/shell.qml @@ -0,0 +1,6 @@ +//@ pragma UseQApplication +import Quickshell + +Scope { + Bar {} +}