diff options
| author | Petri Hienonen <petri.hienonen@gmail.com> | 2024-05-23 13:56:00 +0300 |
|---|---|---|
| committer | Petri Hienonen <petri.hienonen@gmail.com> | 2025-11-30 12:29:57 +0200 |
| commit | 08297376a85a1719518507e54fca9de954d2376a (patch) | |
| tree | 3b9c58304b40248533bbb2bb5b7bad2da9da1ff0 /home/quickshell/bar/blocks | |
| parent | 75c2af4aedd2ac5c2cfc74b346625fa4b265541d (diff) | |
| download | nixos-08297376a85a1719518507e54fca9de954d2376a.tar.zst | |
Agenix configuration
Diffstat (limited to 'home/quickshell/bar/blocks')
| -rw-r--r-- | home/quickshell/bar/blocks/ActiveWorkspace.qml | 34 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Battery.qml | 50 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Date.qml | 10 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Datetime.qml | 31 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Icon.qml | 146 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Memory.qml | 31 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Notifications.qml | 34 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Sound.qml | 176 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/SystemTray.qml | 80 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Time.qml | 10 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Workspace.qml | 28 | ||||
| -rw-r--r-- | home/quickshell/bar/blocks/Workspaces.qml | 74 |
12 files changed, 704 insertions, 0 deletions
diff --git a/home/quickshell/bar/blocks/ActiveWorkspace.qml b/home/quickshell/bar/blocks/ActiveWorkspace.qml new file mode 100644 index 0000000..7969212 --- /dev/null +++ b/home/quickshell/bar/blocks/ActiveWorkspace.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell.Io +import Quickshell.Hyprland +import "../" + +BarText { + // text: { + // var str = activeWindowTitle + // return str.length > chopLength ? str.slice(0, chopLength) + '...' : str; + // } + + property int chopLength + property string activeWindowTitle + + Process { + id: titleProc + command: ["sh", "-c", "hyprctl activewindow | grep title: | sed 's/^[^:]*: //'"] + running: true + + stdout: SplitParser { + onRead: data => activeWindowTitle = data + } + } + + Component.onCompleted: { + Hyprland.rawEvent.connect(hyprEvent) + } + + function hyprEvent(e) { + titleProc.running = true + } +} + diff --git a/home/quickshell/bar/blocks/Battery.qml b/home/quickshell/bar/blocks/Battery.qml new file mode 100644 index 0000000..dd52f7f --- /dev/null +++ b/home/quickshell/bar/blocks/Battery.qml @@ -0,0 +1,50 @@ +import QtQuick +import Quickshell.Io +import "../" + +BarBlock { + property string battery + property bool hasBattery: false + visible: hasBattery + + content: BarText { + symbolText: battery + } + + Process { + id: batteryCheck + command: ["sh", "-c", "test -d /sys/class/power_supply/BAT*"] + running: true + onExited: function(exitCode) { hasBattery = exitCode === 0 } + } + + Process { + id: batteryProc + // Modify command to get both capacity and status in one call + command: ["sh", "-c", "echo $(cat /sys/class/power_supply/BAT*/capacity),$(cat /sys/class/power_supply/BAT*/status)"] + running: hasBattery + + stdout: SplitParser { + onRead: function(data) { + const [capacityStr, status] = data.trim().split(',') + const capacity = parseInt(capacityStr) + let batteryIcon = "" + if (capacity <= 20) batteryIcon = "" + else if (capacity <= 40) batteryIcon = "" + else if (capacity <= 60) batteryIcon = "" + else if (capacity <= 80) batteryIcon = "" + else batteryIcon = "" + + const symbol = status === "Charging" ? "🔌" : batteryIcon + battery = `${symbol} ${capacity}%` + } + } + } + + Timer { + interval: 1000 + running: hasBattery + repeat: true + onTriggered: batteryProc.running = true + } +} diff --git a/home/quickshell/bar/blocks/Date.qml b/home/quickshell/bar/blocks/Date.qml new file mode 100644 index 0000000..11ce193 --- /dev/null +++ b/home/quickshell/bar/blocks/Date.qml @@ -0,0 +1,10 @@ +import QtQuick +import "../" + +BarBlock { + id: text + content: BarText { + symbolText: ` ${Datetime.date}` + } +} + diff --git a/home/quickshell/bar/blocks/Datetime.qml b/home/quickshell/bar/blocks/Datetime.qml new file mode 100644 index 0000000..743e785 --- /dev/null +++ b/home/quickshell/bar/blocks/Datetime.qml @@ -0,0 +1,31 @@ +pragma Singleton + +import Quickshell +import Quickshell.Io +import QtQuick + +Singleton { + property string time; + property string date; + + Process { + id: dateProc + command: ["date", "+%a %e %b|%R"] + running: true + + stdout: SplitParser { + onRead: data => { + date = data.split("|")[0] + time = data.split("|")[1] + } + } + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: dateProc.running = true + } +} + diff --git a/home/quickshell/bar/blocks/Icon.qml b/home/quickshell/bar/blocks/Icon.qml new file mode 100644 index 0000000..c111a7e --- /dev/null +++ b/home/quickshell/bar/blocks/Icon.qml @@ -0,0 +1,146 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import Qt5Compat.GraphicalEffects +import "../" +import "root:/" + +BarBlock { + id: root + Layout.preferredWidth: 20 + + content: BarText { + text: "" + pointSize: 24 + anchors.horizontalCenterOffset: 4 + anchors.verticalCenterOffset: 3 + } + + color: "transparent" + + Process { + id: appListProc + command: ["sh", "-c", "for f in /usr/share/applications/*.desktop; do if ! grep -qi 'terminal=true' \"$f\"; then name=$(grep -i '^Name=' \"$f\" | head -n1 | cut -d= -f2); basename=$(basename \"$f\" .desktop); echo \"$name|$basename|$f\"; fi; done"] + running: false + stdout: SplitParser { + onRead: data => { + const [appName, launchName, desktopFile] = data.trim().split("|") + if (appName && launchName && desktopFile) { + appListModel.append({ name: appName, launchName: launchName, path: desktopFile }) + } + } + } + } + + Process { + id: appLauncher + running: false + command: ["gtk-launch"] + } + + ListModel { + id: appListModel + } + + PopupWindow { + id: menuWindow + width: 300 + height: 400 + visible: false + + anchor { + window: root.QsWindow?.window + edges: Edges.Bottom + gravity: Edges.Top + } + + FocusScope { + anchors.fill: parent + focus: true + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onExited: { + if (!containsMouse) { + closeTimer.start() + } + } + onEntered: closeTimer.stop() + + Timer { + id: closeTimer + interval: 500 + onTriggered: menuWindow.visible = false + } + + Rectangle { + anchors.fill: parent + color: "#2E3440" // Using Nord theme color + border.color: "#4C566A" + border.width: 1 + radius: 4 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 5 + + ListView { + id: appListView + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + model: appListModel + delegate: Rectangle { + width: parent.width + height: 35 + color: mouseArea.containsMouse ? "#4C566A" : "transparent" + radius: 4 + + Text { + anchors.fill: parent + anchors.leftMargin: 10 + text: model.name + color: "white" + font.pixelSize: 12 + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + console.log("Launching:", model.launchName, "from", model.path) + appLauncher.command = ["gtk-launch", model.launchName] + appLauncher.running = true + menuWindow.visible = false + } + } + } + } + } + } + } + } + } + + function filterApps() { + const searchText = searchField.text.toLowerCase() + for (let i = 0; i < appListModel.count; i++) { + const item = appListModel.get(i) + item.visible = item.name.toLowerCase().includes(searchText) + } + } + onClicked: function() { + if (!menuWindow.visible) { + appListModel.clear() + appListProc.running = true + } + menuWindow.visible = !menuWindow.visible + } +}
\ No newline at end of file diff --git a/home/quickshell/bar/blocks/Memory.qml b/home/quickshell/bar/blocks/Memory.qml new file mode 100644 index 0000000..4a931c1 --- /dev/null +++ b/home/quickshell/bar/blocks/Memory.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import Quickshell.Io +import "../" + +BarBlock { + id: text + content: BarText { + symbolText: `- ${Math.floor(percentFree)}%` + } + + property real percentFree + + Process { + id: memProc + command: ["sh", "-c", "free | grep Mem | awk '{print $3/$2 * 100.0}'"] + running: true + + stdout: SplitParser { + onRead: data => percentFree = data + } + } + + Timer { + interval: 2000 + running: true + repeat: true + onTriggered: memProc.running = true + } +} diff --git a/home/quickshell/bar/blocks/Notifications.qml b/home/quickshell/bar/blocks/Notifications.qml new file mode 100644 index 0000000..3871cc4 --- /dev/null +++ b/home/quickshell/bar/blocks/Notifications.qml @@ -0,0 +1,34 @@ +import QtQuick +import Quickshell.Services.Notifications +import "../" + +BarBlock { + id: root + property bool showNotification: false + + text: " " + notifServer.trackedNotifications.values.length + onClicked: function() { + showNotification = !showNotification + } + + NotificationServer { + id: notifServer + onNotification: (notification) => { + notification.tracked = true + } + } + + NotificationPanel { + text_color: root.color + visible: showNotification + + anchors { + top: parent.top + } + + margins { + top: 10 + } + } +} + diff --git a/home/quickshell/bar/blocks/Sound.qml b/home/quickshell/bar/blocks/Sound.qml new file mode 100644 index 0000000..e8be70b --- /dev/null +++ b/home/quickshell/bar/blocks/Sound.qml @@ -0,0 +1,176 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import Quickshell.Services.Pipewire +import Quickshell.Io +import "../" +import "root:/" + +BarBlock { + id: root + property var sink: Pipewire.defaultAudioSink + + PwObjectTracker { + objects: [Pipewire.defaultAudioSink] + onObjectsChanged: { + sink = Pipewire.defaultAudioSink + if (sink?.audio) { + sink.audio.volumeChanged.connect(updateVolume) + } + } + } + + function updateVolume() { + if (sink?.audio) { + const icon = sink.audio.muted ? "" : "" + content.symbolText = `${icon} ${Math.round(sink.audio.volume * 100)}%` + } + } + + content: BarText { symbolText: `${sink?.audio?.muted ? "" : ""} ${Math.round(sink?.audio?.volume * 100)}%` } + + MouseArea { + anchors.fill: parent + onClicked: toggleMenu() + onWheel: function(event) { + if (sink?.audio) { + sink.audio.volume = Math.max(0, Math.min(1, sink.audio.volume + (event.angleDelta.y / 120) * 0.05)) + } + } + } + + Process { + id: pavucontrol + command: ["pavucontrol"] + running: false + } + + PopupWindow { + id: menuWindow + width: 200 + height: 150 + visible: false + + anchor { + window: root.QsWindow?.window + edges: Edges.Bottom + gravity: Edges.Top + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onExited: { + if (!containsMouse) { + closeTimer.start() + } + } + onEntered: closeTimer.stop() + + Timer { + id: closeTimer + interval: 500 + onTriggered: menuWindow.visible = false + } + + Rectangle { + anchors.fill: parent + color: "#2c2c2c" + border.color: "#3c3c3c" + border.width: 1 + radius: 4 + + Column { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + // Volume Slider + Rectangle { + width: parent.width + height: 35 + color: "transparent" + + Slider { + id: volumeSlider + anchors.fill: parent + from: 0 + to: 1 + value: sink?.audio?.volume || 0 + onValueChanged: { + if (sink?.audio) { + sink.audio.volume = value + } + } + + background: Rectangle { + x: volumeSlider.leftPadding + y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 + width: volumeSlider.availableWidth + height: 4 + radius: 2 + color: "#3c3c3c" + + Rectangle { + width: volumeSlider.visualPosition * parent.width + height: parent.height + color: "#4a9eff" + radius: 2 + } + } + + handle: Rectangle { + x: volumeSlider.leftPadding + volumeSlider.visualPosition * (volumeSlider.availableWidth - width) + y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 + width: 16 + height: 16 + radius: 8 + color: volumeSlider.pressed ? "#4a9eff" : "#ffffff" + border.color: "#3c3c3c" + } + } + } + + Repeater { + model: [ + { text: sink?.audio?.muted ? "Unmute" : "Mute", action: () => sink?.audio && (sink.audio.muted = !sink.audio.muted) }, + { text: "Pavucontrol", action: () => { pavucontrol.running = true; menuWindow.visible = false } } + ] + + Rectangle { + width: parent.width + height: 35 + color: mouseArea.containsMouse ? "#3c3c3c" : "transparent" + radius: 4 + + Text { + anchors.fill: parent + anchors.leftMargin: 10 + text: modelData.text + color: "white" + font.pixelSize: 12 + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + modelData.action() + } + } + } + } + } + } + } + } + + function toggleMenu() { + if (root.QsWindow?.window?.contentItem) { + menuWindow.anchor.rect = root.QsWindow.window.contentItem.mapFromItem(root, 0, -menuWindow.height - 5, root.width, root.height) + menuWindow.visible = !menuWindow.visible + } + } +}
\ No newline at end of file diff --git a/home/quickshell/bar/blocks/SystemTray.qml b/home/quickshell/bar/blocks/SystemTray.qml new file mode 100644 index 0000000..15c4691 --- /dev/null +++ b/home/quickshell/bar/blocks/SystemTray.qml @@ -0,0 +1,80 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Quickshell.Services.SystemTray +import "root:/bar" + +RowLayout { + spacing: 5 + + Repeater { + model: ScriptModel { + values: {[...SystemTray.items.values] + .filter((item) => { + return (item.id != "spotify-client" + && item.id != "chrome_status_icon_1") + }) + } + } + + MouseArea { + id: delegate + required property SystemTrayItem modelData + property alias item: delegate.modelData + + Layout.fillHeight: true + implicitWidth: icon.implicitWidth + 5 + + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + hoverEnabled: true + + onClicked: event => { + if (event.button == Qt.LeftButton) { + item.activate(); + } else if (event.button == Qt.MiddleButton) { + item.secondaryActivate(); + } else if (event.button == Qt.RightButton) { + menuAnchor.open(); + } + } + + onWheel: event => { + event.accepted = true; + const points = event.angleDelta.y / 120 + item.scroll(points, false); + } + + IconImage { + id: icon + anchors.centerIn: parent + source: item.icon + implicitSize: 16 + } + + QsMenuAnchor { + id: menuAnchor + menu: item.menu + + anchor.window: delegate.QsWindow.window + anchor.adjustment: PopupAdjustment.Flip + + anchor.onAnchoring: { + const window = delegate.QsWindow.window; + const widgetRect = window.contentItem.mapFromItem(delegate, 0, delegate.height, delegate.width, delegate.height); + + menuAnchor.anchor.rect = widgetRect; + } + } + + Tooltip { + relativeItem: delegate.containsMouse ? delegate : null + + Label { + text: delegate.item.tooltipTitle || delegate.item.id + } + } + } + } +} diff --git a/home/quickshell/bar/blocks/Time.qml b/home/quickshell/bar/blocks/Time.qml new file mode 100644 index 0000000..5650fcb --- /dev/null +++ b/home/quickshell/bar/blocks/Time.qml @@ -0,0 +1,10 @@ +import QtQuick +import "../" + +BarBlock { + id: text + content: BarText { + symbolText: ` ${Datetime.time}` + } +} + diff --git a/home/quickshell/bar/blocks/Workspace.qml b/home/quickshell/bar/blocks/Workspace.qml new file mode 100644 index 0000000..232a3f3 --- /dev/null +++ b/home/quickshell/bar/blocks/Workspace.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Layouts + +Rectangle { + id: ws + + property bool hovered: false + + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Layout.minimumWidth: 10 + Layout.minimumHeight: 10 + Layout.alignment: Qt.AlignHCenter + radius: height / 2 + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: () => { + ws.hovered = true; + } + onExited: () => { + ws.hovered = false; + } + onClicked: () => console.log(`workspace ?`) + } +} diff --git a/home/quickshell/bar/blocks/Workspaces.qml b/home/quickshell/bar/blocks/Workspaces.qml new file mode 100644 index 0000000..66d48a6 --- /dev/null +++ b/home/quickshell/bar/blocks/Workspaces.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import Quickshell.Widgets +import Qt5Compat.GraphicalEffects +import "../utils" as Utils +import "root:/" + +RowLayout { + property HyprlandMonitor monitor: Hyprland.monitorFor(screen) + + Rectangle { + id: workspaceBar + Layout.preferredWidth: Math.max(50, Utils.HyprlandUtils.maxWorkspace * 25) + Layout.preferredHeight: 23 + radius: 7 + color: Theme.get.barBgColor + + Row { + anchors.centerIn: parent + spacing: 15 + + Repeater { + model: Utils.HyprlandUtils.maxWorkspace || 1 + + Item { + required property int index + property bool focused: Hyprland.focusedMonitor?.activeWorkspace?.id === (index + 1) + + width: workspaceText.width + height: workspaceText.height + + Text { + id: workspaceText + text: (index + 1).toString() + color: "white" + font.pixelSize: 15 + font.bold: focused + } + + Rectangle { + visible: focused + anchors { + left: workspaceText.left + right: workspaceText.right + top: workspaceText.bottom + topMargin: -3 + } + height: 2 + color: "white" + } + + DropShadow { + visible: focused + anchors.fill: workspaceText + horizontalOffset: 2 + verticalOffset: 2 + radius: 8.0 + samples: 20 + color: "#000000" + source: workspaceText + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: Utils.HyprlandUtils.switchWorkspace(index + 1) + } + } + } + } + } +}
\ No newline at end of file |
