aboutsummaryrefslogtreecommitdiffstats
path: root/home/quickshell/bar/blocks
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2024-05-23 13:56:00 +0300
committerPetri Hienonen <petri.hienonen@gmail.com>2025-11-30 12:29:57 +0200
commit08297376a85a1719518507e54fca9de954d2376a (patch)
tree3b9c58304b40248533bbb2bb5b7bad2da9da1ff0 /home/quickshell/bar/blocks
parent75c2af4aedd2ac5c2cfc74b346625fa4b265541d (diff)
downloadnixos-08297376a85a1719518507e54fca9de954d2376a.tar.zst
Agenix configuration
Diffstat (limited to 'home/quickshell/bar/blocks')
-rw-r--r--home/quickshell/bar/blocks/ActiveWorkspace.qml34
-rw-r--r--home/quickshell/bar/blocks/Battery.qml50
-rw-r--r--home/quickshell/bar/blocks/Date.qml10
-rw-r--r--home/quickshell/bar/blocks/Datetime.qml31
-rw-r--r--home/quickshell/bar/blocks/Icon.qml146
-rw-r--r--home/quickshell/bar/blocks/Memory.qml31
-rw-r--r--home/quickshell/bar/blocks/Notifications.qml34
-rw-r--r--home/quickshell/bar/blocks/Sound.qml176
-rw-r--r--home/quickshell/bar/blocks/SystemTray.qml80
-rw-r--r--home/quickshell/bar/blocks/Time.qml10
-rw-r--r--home/quickshell/bar/blocks/Workspace.qml28
-rw-r--r--home/quickshell/bar/blocks/Workspaces.qml74
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