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/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/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..3139cbf7 100644 --- a/modules/widgets/defaultview/CompactPlayer.qml +++ b/modules/widgets/defaultview/CompactPlayer.qml @@ -124,24 +124,62 @@ 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 && !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" + ] + + stdout: SplitParser { + splitMarker: "\n" + onRead: data => { + const trimmed = data.trim(); + if (trimmed === "") return; + const parts = trimmed.split(";"); + const parsed = parts.map(x => parseInt(x, 10) || 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 +189,72 @@ Item { easing.type: Easing.OutQuart } } + + Text { + id: mediaTitle + 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 + 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 { 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