From 9be8ba74b5c7469950834a600e51ae9b874cf137 Mon Sep 17 00:00:00 2001 From: Nam Anh Date: Mon, 16 Mar 2026 21:37:06 +0700 Subject: [PATCH 1/3] feat(notch): support cava --- config/Config.qml | 16 +-- config/defaults/notch.js | 4 +- .../widgets/dashboard/controls/ShellPanel.qml | 25 +++++ modules/widgets/defaultview/CompactPlayer.qml | 106 ++++++++++++++++-- 4 files changed, 133 insertions(+), 18 deletions(-) diff --git a/config/Config.qml b/config/Config.qml index b9a581ec..c93f1556 100644 --- a/config/Config.qml +++ b/config/Config.qml @@ -624,6 +624,8 @@ Singleton { property string noMediaDisplay: "userHost" property string customText: "Ambxst" property bool disableHoverExpansion: true + property bool showCavaOnMedia: false + property int cavaBars: 16 } } @@ -1099,7 +1101,7 @@ Singleton { if (current.ambxst.dashboard && typeof current.ambxst.dashboard === "object" && !current.ambxst.dashboard.modifiers) { console.log("Migrating nested ambxst binds to flat structure..."); const nested = current.ambxst.dashboard; - + // Map old names to new names and update arguments if (nested.widgets) { current.ambxst.launcher = nested.widgets; @@ -3037,7 +3039,7 @@ Singleton { // Trigger save GlobalStates.markShellChanged(); } - } + } // If notch moves top else if (notchPosition === "top") { // Restore Dock if displaced @@ -3139,20 +3141,20 @@ Singleton { function resolveColor(colorValue) { if (!colorValue) return "transparent"; // Fallback - + if (isHexColor(colorValue)) { return colorValue; } - + // Check Colors singleton if (typeof Colors === 'undefined' || !Colors) return "transparent"; - - return Colors[colorValue] || "transparent"; + + return Colors[colorValue] || "transparent"; } function resolveColorWithOpacity(colorValue, opacity) { if (!colorValue) return Qt.rgba(0,0,0,0); - + const color = isHexColor(colorValue) ? Qt.color(colorValue) : (Colors[colorValue] || Qt.color("transparent")); return Qt.rgba(color.r, color.g, color.b, opacity); } diff --git a/config/defaults/notch.js b/config/defaults/notch.js index decb7159..74a0e20c 100644 --- a/config/defaults/notch.js +++ b/config/defaults/notch.js @@ -7,5 +7,7 @@ var data = { "keepHidden": false, "noMediaDisplay": "userHost", "customText": "Ambxst", - "disableHoverExpansion": true + "disableHoverExpansion": true, + "showCavaOnMedia": false, + "cavaBars": 16 } diff --git a/modules/widgets/dashboard/controls/ShellPanel.qml b/modules/widgets/dashboard/controls/ShellPanel.qml index d02bf8cf..d37b6319 100644 --- a/modules/widgets/dashboard/controls/ShellPanel.qml +++ b/modules/widgets/dashboard/controls/ShellPanel.qml @@ -1125,6 +1125,31 @@ Item { } } } + + ToggleRow { + label: "Cava Visualizer on Media" + checked: Config.notch.showCavaOnMedia ?? false + onToggled: value => { + if (value !== Config.notch.showCavaOnMedia) { + GlobalStates.markShellChanged(); + Config.notch.showCavaOnMedia = value; + } + } + } + + NumberInputRow { + label: "Cava Bars" + visible: Config.notch.showCavaOnMedia ?? false + value: Config.notch.cavaBars ?? 16 + minValue: 4 + maxValue: 32 + onValueEdited: newValue => { + if (newValue !== Config.notch.cavaBars) { + GlobalStates.markShellChanged(); + Config.notch.cavaBars = newValue; + } + } + } } Separator { diff --git a/modules/widgets/defaultview/CompactPlayer.qml b/modules/widgets/defaultview/CompactPlayer.qml index 265b87e8..d2599363 100644 --- a/modules/widgets/defaultview/CompactPlayer.qml +++ b/modules/widgets/defaultview/CompactPlayer.qml @@ -124,24 +124,44 @@ Item { } } + // Cava visualizer support + readonly property bool cavaEnabled: (Config.notch.showCavaOnMedia ?? false) && isPlaying && player !== null + property var cavaBars: [] + + Process { + id: cavaProcess + running: compactPlayer.cavaEnabled && compactPlayer.visible + command: [ + "bash", "-c", + "printf '[general]\\nbars=" + (Config.notch.cavaBars ?? 16) + "\\n[output]\\nmethod=raw\\nraw_target=/dev/stdout\\ndata_format=ascii\\nascii_max_range=7\\nbar_delimiter=59\\nframe_delimiter=10\\n' > /tmp/ambxst-cava.ini && exec cava -p /tmp/ambxst-cava.ini" + ] + + stdout: SplitParser { + splitMarker: "\n" + onRead: data => { + const trimmed = data.trim(); + if (trimmed === "") return; + const parts = trimmed.split(";"); + const parsed = parts.map(x => parseInt(x) || 0); + if (parsed.length > 0) { + compactPlayer.cavaBars = parsed; + } + } + } + } + StyledRect { variant: "common" anchors.fill: parent radius: Styling.radius(-4) - Text { - id: mediaTitle + Item { + id: mediaTitleContainer anchors.centerIn: parent - width: parent.width - 32 - text: compactPlayer.displayedTitle - font.family: Config.theme.font - font.pixelSize: Styling.fontSize(0) - font.bold: true - color: Colors.overBackground - elide: Text.ElideRight + width: parent.width + height: parent.height visible: opacity > 0 opacity: (compactPlayer.notchHovered && compactPlayer.player) ? 0.0 : 1.0 - horizontalAlignment: Text.AlignHCenter z: 5 Behavior on opacity { @@ -151,6 +171,72 @@ Item { easing.type: Easing.OutQuart } } + + Text { + id: mediaTitle + anchors.centerIn: parent + width: parent.width + text: compactPlayer.displayedTitle + font.family: Config.theme.font + font.pixelSize: Styling.fontSize(0) + font.bold: true + color: Colors.overBackground + elide: Text.ElideRight + visible: opacity > 0 + opacity: compactPlayer.cavaEnabled ? 0.0 : 1.0 + horizontalAlignment: Text.AlignHCenter + + Behavior on opacity { + enabled: Config.animDuration > 0 + NumberAnimation { + duration: Config.animDuration + easing.type: Easing.OutQuart + } + } + } + + // Cava bars visualizer (shown in place of title when enabled + playing) + Row { + id: cavaBarsRow + anchors.fill: parent + anchors.margins: 8 + spacing: Math.max(1, (parent.width - anchors.margins * 2 - compactPlayer.cavaBars.length * barWidth) / Math.max(1, compactPlayer.cavaBars.length - 1)) + visible: opacity > 0 + opacity: compactPlayer.cavaEnabled ? 1.0 : 0.0 + + Behavior on opacity { + enabled: Config.animDuration > 0 + NumberAnimation { + duration: Config.animDuration + easing.type: Easing.OutQuart + } + } + + readonly property real barWidth: Math.max(2, Math.floor((parent.width - anchors.margins * 2) / Math.max(1, compactPlayer.cavaBars.length) - 1)) + + Repeater { + model: compactPlayer.cavaBars.length + Item { + required property int index + width: cavaBarsRow.barWidth + height: cavaBarsRow.height + Rectangle { + width: parent.width + height: Math.max(2, (compactPlayer.cavaBars[index] / 7.0) * cavaBarsRow.height) + anchors.bottom: parent.bottom + color: Colors.overBackground + radius: 1 + Behavior on height { + enabled: Config.animDuration > 0 + NumberAnimation { + duration: 50 + easing.type: Easing.OutQuart + } + } + } + } + } + } } ClippingRectangle { From 3366fc46935f342be38cb8c7351f1b1ce8a5adf6 Mon Sep 17 00:00:00 2001 From: Nam Anh Date: Mon, 16 Mar 2026 22:06:32 +0700 Subject: [PATCH 2/3] feat: install cava as dep & restart on config change --- install.sh | 2 +- modules/widgets/defaultview/CompactPlayer.qml | 22 +++++++++++++++++-- nix/packages/media.nix | 1 + 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index b638826e..324b1956 100755 --- a/install.sh +++ b/install.sh @@ -165,7 +165,7 @@ install_dependencies() { local PKGS=( kitty tmux fuzzel network-manager-applet blueman - pipewire wireplumber pavucontrol easyeffects ffmpeg x264 playerctl + pipewire wireplumber pavucontrol easyeffects cava ffmpeg x264 playerctl qt6-base qt6-declarative qt6-wayland qt6-svg qt6-tools qt6-imageformats qt6-multimedia qt6-shadertools libwebp libavif syntax-highlighting breeze-icons hicolor-icon-theme brightnessctl ddcutil fontconfig grim slurp imagemagick jq sqlite upower diff --git a/modules/widgets/defaultview/CompactPlayer.qml b/modules/widgets/defaultview/CompactPlayer.qml index d2599363..032eb160 100644 --- a/modules/widgets/defaultview/CompactPlayer.qml +++ b/modules/widgets/defaultview/CompactPlayer.qml @@ -127,10 +127,28 @@ Item { // Cava visualizer support readonly property bool cavaEnabled: (Config.notch.showCavaOnMedia ?? false) && isPlaying && player !== null property var cavaBars: [] + property bool cavaRestartHold: false + + Timer { + id: cavaRestartTimer + interval: 40 + repeat: false + onTriggered: compactPlayer.cavaRestartHold = false + } + + Connections { + target: Config.notch + function onCavaBarsChanged() { + if (!compactPlayer.cavaEnabled || !compactPlayer.visible) + return; + compactPlayer.cavaRestartHold = true; + cavaRestartTimer.restart(); + } + } Process { id: cavaProcess - running: compactPlayer.cavaEnabled && compactPlayer.visible + running: compactPlayer.cavaEnabled && compactPlayer.visible && !compactPlayer.cavaRestartHold command: [ "bash", "-c", "printf '[general]\\nbars=" + (Config.notch.cavaBars ?? 16) + "\\n[output]\\nmethod=raw\\nraw_target=/dev/stdout\\ndata_format=ascii\\nascii_max_range=7\\nbar_delimiter=59\\nframe_delimiter=10\\n' > /tmp/ambxst-cava.ini && exec cava -p /tmp/ambxst-cava.ini" @@ -142,7 +160,7 @@ Item { const trimmed = data.trim(); if (trimmed === "") return; const parts = trimmed.split(";"); - const parsed = parts.map(x => parseInt(x) || 0); + const parsed = parts.map(x => parseInt(x, 10) || 0); if (parsed.length > 0) { compactPlayer.cavaBars = parsed; } diff --git a/nix/packages/media.nix b/nix/packages/media.nix index 56074e79..eee88b9f 100644 --- a/nix/packages/media.nix +++ b/nix/packages/media.nix @@ -2,6 +2,7 @@ { pkgs }: with pkgs; [ + cava gpu-screen-recorder mpvpaper From 0dd1300266e4e1c92460ee23571c691bbef18863 Mon Sep 17 00:00:00 2001 From: Nam Anh Date: Tue, 17 Mar 2026 12:07:13 +0700 Subject: [PATCH 3/3] style: return padding to text --- modules/widgets/defaultview/CompactPlayer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/widgets/defaultview/CompactPlayer.qml b/modules/widgets/defaultview/CompactPlayer.qml index 032eb160..3139cbf7 100644 --- a/modules/widgets/defaultview/CompactPlayer.qml +++ b/modules/widgets/defaultview/CompactPlayer.qml @@ -193,7 +193,7 @@ Item { Text { id: mediaTitle anchors.centerIn: parent - width: parent.width + width: parent.width - 32 text: compactPlayer.displayedTitle font.family: Config.theme.font font.pixelSize: Styling.fontSize(0)