From 7922ded35ff292e485c3369dab9726b9142ce526 Mon Sep 17 00:00:00 2001 From: coast Date: Mon, 7 Sep 2026 07:07:22 +0330 Subject: a lot of quickshell --- .../config/quickshell/modules/bar/Bar.qml | 154 ++++++ .../config/quickshell/modules/bar/Music.qml | 219 +++++++++ .../config/quickshell/modules/bar/QuickControl.qml | 519 +++++++++++++++++++++ .../config/quickshell/modules/bar/Time.qml | 144 ++++++ .../config/quickshell/modules/bar/Workspaces.qml | 169 +++++++ 5 files changed, 1205 insertions(+) create mode 100644 home-configuration/config/quickshell/modules/bar/Bar.qml create mode 100644 home-configuration/config/quickshell/modules/bar/Music.qml create mode 100644 home-configuration/config/quickshell/modules/bar/QuickControl.qml create mode 100644 home-configuration/config/quickshell/modules/bar/Time.qml create mode 100644 home-configuration/config/quickshell/modules/bar/Workspaces.qml (limited to 'home-configuration/config/quickshell/modules/bar') diff --git a/home-configuration/config/quickshell/modules/bar/Bar.qml b/home-configuration/config/quickshell/modules/bar/Bar.qml new file mode 100644 index 0000000..3fc6b36 --- /dev/null +++ b/home-configuration/config/quickshell/modules/bar/Bar.qml @@ -0,0 +1,154 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import "../../config.js" as Config + +PanelWindow { + id: bar + + anchors { + top: true + left: true + right: true + } + + implicitHeight: Config.barHeight + color: "transparent" + + Rectangle { + id: content + + anchors.fill: parent + color: Config.barBackground + opacity: 0 + y: -Config.barHeight + + RowLayout { + anchors { + left: parent.left + leftMargin: Config.barMargin + verticalCenter: parent.verticalCenter + } + + spacing: Config.workspaceSpacing + + Loader { + active: true + sourceComponent: Workspaces {} + } + } + + Rectangle { + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + + height: Config.blockHeight + width: windowTitle.contentWidth + Config.blockPadding + radius: Config.blockRadius + color: "transparent" + + Text { + id: windowTitle + + anchors.centerIn: parent + + text: niri.focusedWindow?.title ?? "" + + color: Config.textWindow + + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + + elide: Text.ElideRight + } + } + + RowLayout { + id: rightLayout + + anchors { + right: parent.right + rightMargin: Config.barMargin + verticalCenter: parent.verticalCenter + } + + spacing: Config.workspaceSpacing + + Loader { + active: true + sourceComponent: Music {} + } + + Loader { + active: true + sourceComponent: Time {} + } + + Loader { + active: true + sourceComponent: QuickControl {} + } + } + } + + ParallelAnimation { + id: showAnimation + + NumberAnimation { + target: content + property: "y" + from: -Config.barHeight + to: 0 + duration: Config.barAnimationDuration + easing.type: Easing.OutCubic + } + + NumberAnimation { + target: content + property: "opacity" + from: 0 + to: 1 + duration: Config.barAnimationDuration + easing.type: Easing.OutCubic + } + } + + ParallelAnimation { + id: hideAnimation + + NumberAnimation { + target: content + property: "y" + from: 0 + to: -Config.barHeight + duration: Config.barAnimationDuration + easing.type: Easing.InCubic + } + + NumberAnimation { + target: content + property: "opacity" + from: 1 + to: 0 + duration: Config.barAnimationDuration + easing.type: Easing.InCubic + } + + onFinished: { + showAnimation.start() + } + } + + Component.onCompleted: { + showAnimation.start() + } + + function restart() { + showAnimation.stop() + hideAnimation.stop() + hideAnimation.start() + } +} diff --git a/home-configuration/config/quickshell/modules/bar/Music.qml b/home-configuration/config/quickshell/modules/bar/Music.qml new file mode 100644 index 0000000..ec16fed --- /dev/null +++ b/home-configuration/config/quickshell/modules/bar/Music.qml @@ -0,0 +1,219 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Mpris +import "../../config.js" as Config + +Rectangle { + id: musicBlock + + property var player: null + + implicitWidth: visible + ? Math.min(textMetrics.width + Config.blockPadding + 8, 400) + : 0 + + implicitHeight: Config.blockHeight + + radius: Config.blockRadius + + visible: player !== null && player.isPlaying + + function findFeishin() { + player = null + + for (var i = 0; i < Mpris.players.values.length; i++) { + var p = Mpris.players.values[i] + + console.log( + "MPRIS:", + "identity =", p.identity, + "desktopEntry =", p.desktopEntry + ) + + var identity = (p.identity || "").toLowerCase() + var desktopEntry = (p.desktopEntry || "").toLowerCase() + + if ( + identity === "feishin" || + desktopEntry === "feishin" + ) { + player = p + console.log("FEISHIN FOUND") + return + } + } + + console.log("FEISHIN NOT FOUND") + } + + function cleanTitle(title) { + if (!title) + return "" + + return title + .replace(/\s*[\(\[][^)\]]*\blive\b[^)\]]*[\)\]]/gi, "") + .replace(/\s*[-–—]\s*\blive\b.*$/i, "") + .trim() + } + + Component.onCompleted: { + findFeishin() + } + + Connections { + target: Mpris.players + + function onValuesChanged() { + findFeishin() + } + } + + gradient: Gradient { + GradientStop { + id: stop1 + position: 0.0 + color: Config.accentGreen + } + + GradientStop { + id: stop2 + position: 0.33 + color: Config.accentYellow + } + + GradientStop { + id: stop3 + position: 0.66 + color: Config.accentOrange + } + + GradientStop { + id: stop4 + position: 1.0 + color: Config.accentRed + } + } + + TextMetrics { + id: textMetrics + + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + + text: player + ? "󰎇 " + player.trackArtist + " — " + cleanTitle(player.trackTitle) + : "" + } + + Text { + id: musicText + + anchors { + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + } + + leftPadding: Config.blockPadding / 2 + rightPadding: Config.blockPadding / 2 + + text: player + ? "󰎇 " + player.trackArtist + " — " + cleanTitle(player.trackTitle) + : "" + + color: Config.textDark + + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + + elide: Text.ElideRight + maximumLineCount: 1 + } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + + onClicked: { + if (player && player.canRaise) + player.raise() + } + } + + SequentialAnimation { + running: true + loops: Animation.Infinite + + ParallelAnimation { + ColorAnimation { + target: stop1 + property: "color" + from: Config.accentGreen + to: Config.accentRed + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop2 + property: "color" + from: Config.accentYellow + to: Config.accentGreen + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop3 + property: "color" + from: Config.accentOrange + to: Config.accentYellow + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop4 + property: "color" + from: Config.accentRed + to: Config.accentOrange + duration: Config.gradientAnimationDuration + } + } + + ParallelAnimation { + ColorAnimation { + target: stop1 + property: "color" + from: Config.accentRed + to: Config.accentGreen + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop2 + property: "color" + from: Config.accentGreen + to: Config.accentYellow + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop3 + property: "color" + from: Config.accentYellow + to: Config.accentOrange + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop4 + property: "color" + from: Config.accentOrange + to: Config.accentRed + duration: Config.gradientAnimationDuration + } + } + } +} diff --git a/home-configuration/config/quickshell/modules/bar/QuickControl.qml b/home-configuration/config/quickshell/modules/bar/QuickControl.qml new file mode 100644 index 0000000..ffe6f6b --- /dev/null +++ b/home-configuration/config/quickshell/modules/bar/QuickControl.qml @@ -0,0 +1,519 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pipewire +import Quickshell.Services.SystemTray +import "../../config.js" as Config + +Rectangle { + id: root + + property bool expanded: false + + implicitWidth: layout.implicitWidth + 24 + implicitHeight: Config.blockHeight + + radius: Config.blockRadius + + gradient: Gradient { + GradientStop { + id: stop1 + position: 0.0 + color: Config.accentGreen + } + + GradientStop { + id: stop2 + position: 0.33 + color: Config.accentYellow + } + + GradientStop { + id: stop3 + position: 0.66 + color: Config.accentOrange + } + + GradientStop { + id: stop4 + position: 1.0 + color: Config.accentRed + } + } + + Behavior on implicitWidth { + NumberAnimation { + duration: 250 + easing.type: Easing.OutCubic + } + } + + Component.onCompleted: { + console.info("QuickControl: loaded") + console.info( + "QuickControl: tray items =", + SystemTray.items.values.length + ) + + if (Pipewire.defaultAudioSink) { + console.info( + "QuickControl: audio sink =", + Pipewire.defaultAudioSink.name + ) + + if (Pipewire.defaultAudioSink.audio) { + console.info( + "QuickControl: volume =", + Math.round( + Pipewire.defaultAudioSink.audio.volume * 100 + ) + "%", + "muted =", + Pipewire.defaultAudioSink.audio.muted + ) + } + } else { + console.warn("QuickControl: no default audio sink") + } + } + + onExpandedChanged: { + console.info( + "QuickControl: expanded =", + expanded + ) + } + + RowLayout { + id: layout + + anchors { + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + leftMargin: 12 + rightMargin: 12 + } + + spacing: 6 + + Text { + id: volumeIcon + + text: Pipewire.defaultAudioSink?.audio?.muted + ? "󰖁" + : "󰕾" + + visible: root.expanded + + color: Config.textDark + + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + + onClicked: { + if (!Pipewire.defaultAudioSink?.audio) { + console.warn( + "QuickControl: no audio sink" + ) + return + } + + var audio = Pipewire.defaultAudioSink.audio + + audio.muted = !audio.muted + + console.info( + "QuickControl: mute =", + audio.muted + ) + } + } + } + + Text { + id: volumeText + + text: Pipewire.defaultAudioSink?.audio + ? Math.round( + Pipewire.defaultAudioSink.audio.volume * 100 + ) + "%" + : "0%" + + visible: root.expanded + + color: Config.textDark + + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + + onClicked: { + if (!Pipewire.defaultAudioSink?.audio) { + console.warn( + "QuickControl: no audio sink" + ) + return + } + + var audio = Pipewire.defaultAudioSink.audio + + audio.volume = Math.min( + audio.volume + 0.05, + 1.0 + ) + + console.info( + "QuickControl: volume =", + Math.round(audio.volume * 100) + "%" + ) + } + } + } + + Text { + id: brightnessIcon + + text: "󰃠" + + visible: root.expanded + + color: Config.textDark + + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + + onClicked: { + console.info( + "QuickControl: brightness clicked" + ) + + Quickshell.execDetached([ + "brightnessctl", + "set", + "+5%" + ]) + } + } + } + + Text { + id: networkIcon + + text: { + var item = SystemTray.items.values.find(function(item) { + return item.id === "nm-applet" + }) + + if (!item) + return "󰤨" + + var title = (item.title || "").toLowerCase() + + if (title.indexOf("ethernet") !== -1 || + title.indexOf("wired") !== -1 || + title.indexOf("lan") !== -1) + return "󰈀" + + return "󰤨" + } + + visible: root.expanded + color: Config.textDark + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + + onClicked: { + console.info("QuickControl: network clicked") + Quickshell.execDetached(["nm-connection-editor"]) + } + } +} + + Repeater { + model: root.expanded + ? SystemTray.items + : null + + delegate: Item { + id: trayItem + + required property var modelData + + implicitWidth: 18 + implicitHeight: 18 + + Layout.preferredWidth: 18 + Layout.preferredHeight: 18 + + Component.onCompleted: { + console.info( + "QuickControl: tray item:", + modelData.title, + "id =", + modelData.id, + "hasMenu =", + modelData.hasMenu, + "onlyMenu =", + modelData.onlyMenu + ) + } + + QsMenuAnchor { + id: menuAnchor + + anchor.item: trayItem + + menu: modelData.menu + + onOpened: { + console.info( + "QuickControl: context menu opened:", + modelData.title + ) + } + + onClosed: { + console.info( + "QuickControl: context menu closed:", + modelData.title + ) + } + } + + Image { + anchors.fill: parent + + source: modelData.icon + + sourceSize.width: 18 + sourceSize.height: 18 + + fillMode: Image.PreserveAspectFit + } + + MouseArea { + anchors.fill: parent + + acceptedButtons: + Qt.LeftButton | + Qt.RightButton | + Qt.MiddleButton + + cursorShape: Qt.PointingHandCursor + + onClicked: function(mouse) { + if (mouse.button === Qt.RightButton) { + console.info( + "QuickControl: right click:", + modelData.title, + "hasMenu =", + modelData.hasMenu + ) + + if (modelData.hasMenu) { + console.info( + "QuickControl: opening menu:", + modelData.title + ) + + menuAnchor.open() + } else { + console.info( + "QuickControl: no context menu:", + modelData.title + ) + } + + return + } + + if (mouse.button === Qt.MiddleButton) { + console.info( + "QuickControl: middle click:", + modelData.title + ) + + modelData.secondaryActivate() + return + } + + console.info( + "QuickControl: left click:", + modelData.title + ) + + if (modelData.onlyMenu && modelData.hasMenu) { + console.info( + "QuickControl: onlyMenu item, opening menu:", + modelData.title + ) + + menuAnchor.open() + } else { + modelData.activate() + } + } + } + } + } + + Text { + id: dots + + text: "•••" + + color: Config.textDark + + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + + Layout.alignment: Qt.AlignVCenter + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + + onClicked: { + root.expanded = !root.expanded + + console.info( + "QuickControl: toggled, expanded =", + root.expanded + ) + } + } + } + } + + Connections { + target: SystemTray.items + + function onValuesChanged() { + console.info( + "QuickControl: system tray changed, items =", + SystemTray.items.values.length + ) + } + } + + Connections { + target: Pipewire.defaultAudioSink?.audio ?? null + + function onVolumeChanged() { + if (Pipewire.defaultAudioSink?.audio) { + console.info( + "QuickControl: volume changed:", + Math.round( + Pipewire.defaultAudioSink.audio.volume * 100 + ) + "%" + ) + } + } + + function onMutedChanged() { + if (Pipewire.defaultAudioSink?.audio) { + console.info( + "QuickControl: muted changed:", + Pipewire.defaultAudioSink.audio.muted + ) + } + } + } + + SequentialAnimation { + running: true + loops: Animation.Infinite + + ParallelAnimation { + ColorAnimation { + target: stop1 + property: "color" + from: Config.accentGreen + to: Config.accentRed + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop2 + property: "color" + from: Config.accentYellow + to: Config.accentGreen + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop3 + property: "color" + from: Config.accentOrange + to: Config.accentYellow + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop4 + property: "color" + from: Config.accentRed + to: Config.accentOrange + duration: Config.gradientAnimationDuration + } + } + + ParallelAnimation { + ColorAnimation { + target: stop1 + property: "color" + from: Config.accentRed + to: Config.accentGreen + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop2 + property: "color" + from: Config.accentGreen + to: Config.accentYellow + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop3 + property: "color" + from: Config.accentYellow + to: Config.accentOrange + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop4 + property: "color" + from: Config.accentOrange + to: Config.accentRed + duration: Config.gradientAnimationDuration + } + } + } +} diff --git a/home-configuration/config/quickshell/modules/bar/Time.qml b/home-configuration/config/quickshell/modules/bar/Time.qml new file mode 100644 index 0000000..dfa3f0d --- /dev/null +++ b/home-configuration/config/quickshell/modules/bar/Time.qml @@ -0,0 +1,144 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import "../../config.js" as Config + +Rectangle { + id: clockBlock + + implicitWidth: timeText.contentWidth + Config.blockPadding + implicitHeight: Config.blockHeight + + radius: Config.blockRadius + + property bool alternateFormat: false + + gradient: Gradient { + GradientStop { + id: stop1 + position: 0.0 + color: Config.accentGreen + } + + GradientStop { + id: stop2 + position: 0.33 + color: Config.accentYellow + } + + GradientStop { + id: stop3 + position: 0.66 + color: Config.accentOrange + } + + GradientStop { + id: stop4 + position: 1.0 + color: Config.accentRed + } + } + + SystemClock { + id: clock + precision: SystemClock.Seconds + } + + Text { + id: timeText + + anchors.centerIn: parent + + text: alternateFormat + ? Qt.formatDateTime(clock.date, "󱑃 dddd, dd MMMM yyyy") + : Qt.formatDateTime(clock.date, " hh:mm AP • ddd, dd/MM") + + color: Config.textDark + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + + onClicked: { + alternateFormat = !alternateFormat + } + } + + SequentialAnimation { + running: true + loops: Animation.Infinite + + ParallelAnimation { + ColorAnimation { + target: stop1 + property: "color" + from: Config.accentGreen + to: Config.accentRed + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop2 + property: "color" + from: Config.accentYellow + to: Config.accentGreen + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop3 + property: "color" + from: Config.accentOrange + to: Config.accentYellow + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop4 + property: "color" + from: Config.accentRed + to: Config.accentOrange + duration: Config.gradientAnimationDuration + } + } + + ParallelAnimation { + ColorAnimation { + target: stop1 + property: "color" + from: Config.accentRed + to: Config.accentGreen + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop2 + property: "color" + from: Config.accentGreen + to: Config.accentYellow + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop3 + property: "color" + from: Config.accentYellow + to: Config.accentOrange + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop4 + property: "color" + from: Config.accentOrange + to: Config.accentRed + duration: Config.gradientAnimationDuration + } + } + } +} diff --git a/home-configuration/config/quickshell/modules/bar/Workspaces.qml b/home-configuration/config/quickshell/modules/bar/Workspaces.qml new file mode 100644 index 0000000..3962a63 --- /dev/null +++ b/home-configuration/config/quickshell/modules/bar/Workspaces.qml @@ -0,0 +1,169 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import "../../config.js" as Config + +Rectangle { + id: workspaceBlock + + implicitWidth: workspaceRow.width + 16 + implicitHeight: Config.blockHeight + + color: "transparent" + + RowLayout { + id: workspaceRow + + anchors.verticalCenter: parent.verticalCenter + spacing: Config.workspaceSpacing + + Repeater { + model: niri.workspaces + + delegate: Rectangle { + id: workspace + + visible: model.output === Config.barOutput && index < 11 + + implicitWidth: Config.workspaceWidth + implicitHeight: Config.blockHeight + + radius: Config.blockRadius + + color: model.isActive + ? "transparent" + : Config.workspaceBackground + + layer.enabled: true + + Rectangle { + anchors.fill: parent + radius: parent.radius + visible: model.isActive + + gradient: Gradient { + GradientStop { + id: stop1 + position: 0.0 + color: Config.accentGreen + } + + GradientStop { + id: stop2 + position: 0.33 + color: Config.accentYellow + } + + GradientStop { + id: stop3 + position: 0.66 + color: Config.accentOrange + } + + GradientStop { + id: stop4 + position: 1.0 + color: Config.accentRed + } + } + + SequentialAnimation { + running: true + loops: Animation.Infinite + + ParallelAnimation { + ColorAnimation { + target: stop1 + property: "color" + from: Config.accentGreen + to: Config.accentRed + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop2 + property: "color" + from: Config.accentYellow + to: Config.accentGreen + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop3 + property: "color" + from: Config.accentOrange + to: Config.accentYellow + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop4 + property: "color" + from: Config.accentRed + to: Config.accentOrange + duration: Config.gradientAnimationDuration + } + } + + ParallelAnimation { + ColorAnimation { + target: stop1 + property: "color" + from: Config.accentRed + to: Config.accentGreen + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop2 + property: "color" + from: Config.accentGreen + to: Config.accentYellow + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop3 + property: "color" + from: Config.accentYellow + to: Config.accentOrange + duration: Config.gradientAnimationDuration + } + + ColorAnimation { + target: stop4 + property: "color" + from: Config.accentOrange + to: Config.accentRed + duration: Config.gradientAnimationDuration + } + } + } + } + + Text { + anchors.centerIn: parent + + text: index + color: model.isActive + ? Config.textDark + : Config.textInactive + + font.family: Config.fontFamily + font.pixelSize: Config.fontSize + font.bold: true + } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + + onClicked: { + niri.focusWorkspaceById(model.id) + } + } + } + } + } +} -- cgit v1.3