import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs.config import qs.modules.functions import qs.modules.components import qs.services Scope { id: root Variants { model: Quickshell.screens PanelWindow { id: backgroundContainer required property var modelData property string displayName: modelData.name property url wallpaperPath: { const displays = Config.runtime.monitors const fallback = Config.runtime.appearance.background.defaultPath if (!displays) return fallback const monitor = displays?.[displayName] return monitor?.wallpaper ?? fallback } // parallax config property bool parallaxEnabled: Config.runtime.appearance.background.parallax.enabled property real parallaxZoom: Config.runtime.appearance.background.parallax.zoom property int workspaceRange: Config.runtime.bar.modules.workspaces.workspaceIndicators // hyprland property int activeWorkspaceId: Hyprland.focusedWorkspace?.id ?? 1 // wallpaper geometry property real wallpaperWidth: bgImg.implicitWidth property real wallpaperHeight: bgImg.implicitHeight property real wallpaperToScreenRatio: { if (wallpaperWidth <= 0 || wallpaperHeight <= 0) return 1 return Math.min( wallpaperWidth / width, wallpaperHeight / height ) } property real effectiveScale: parallaxEnabled ? parallaxZoom : 1 property real movableXSpace: Math.max( 0, ((wallpaperWidth / wallpaperToScreenRatio * effectiveScale) - width) / 2 ) // workspace mapping property int lowerWorkspace: Math.floor((activeWorkspaceId - 1) / workspaceRange) * workspaceRange + 1 property int upperWorkspace: lowerWorkspace + workspaceRange property int workspaceSpan: Math.max(1, upperWorkspace - lowerWorkspace) property real valueX: { if (!parallaxEnabled) return 0.5 return (activeWorkspaceId - lowerWorkspace) / workspaceSpan } // sidebar globals property bool sidebarLeftOpen: Globals.visiblility.sidebarLeft && Config.runtime.appearance.background.parallax.enableSidebarLeft property bool sidebarRightOpen: Globals.visiblility.sidebarRight && Config.runtime.appearance.background.parallax.enableSidebarRight property real sidebarOffset: { if (sidebarLeftOpen && !sidebarRightOpen) if (Config.runtime.bar.position === "right") return 0.15 else return -0.15 if (sidebarRightOpen && !sidebarLeftOpen) if (Config.runtime.bar.position === "left") return -0.15 else return 0.15 return 0 } property real effectiveValueX: Math.max( 0, Math.min( 1, valueX + sidebarOffset ) ) // window color: (bgImg.status === Image.Error) ? Appearance.colors.colLayer2 : "transparent" WlrLayershell.namespace: "nucleus:background" exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: WlrLayer.Background screen: modelData visible: Config.initialized && Config.runtime.appearance.background.enabled anchors { top: true left: true right: true bottom: true } // wallpaper picker Process { id: wallpaperProc command: ["bash", "-c", Directories.scriptsPath + "/interface/changebg.sh"] stdout: StdioCollector { onStreamFinished: { const out = text.trim() if (out !== "null" && out.length > 0) { const parts = out.split("|") if (parts.length === 2) { const monitor = parts[0] const wallpaper = parts[1] Config.updateKey( "monitors." + monitor + ".wallpaper", wallpaper ) } } Quickshell.execDetached([ "nucleus", "ipc", "call", "clock", "changePosition" ]) if (Config.runtime.appearance.colors.autogenerated) { Quickshell.execDetached([ "nucleus", "ipc", "call", "global", "regenColors" ]); } } } } // wallpaper Item { anchors.fill: parent clip: true StyledImage { id: bgImg visible: status === Image.Ready smooth: false cache: false fillMode: Image.PreserveAspectCrop source: wallpaperPath + "?t=" + Date.now() width: wallpaperWidth / wallpaperToScreenRatio * effectiveScale height: wallpaperHeight / wallpaperToScreenRatio * effectiveScale x: -movableXSpace - (effectiveValueX - 0.5) * 2 * movableXSpace y: 0 Behavior on x { NumberAnimation { duration: Metrics.chronoDuration(600) easing.type: Easing.OutCubic } } onStatusChanged: { if (status === Image.Ready) { backgroundContainer.wallpaperWidth = implicitWidth backgroundContainer.wallpaperHeight = implicitHeight } } } MouseArea { id: widgetCanvas anchors.fill: parent } // error ui Item { anchors.centerIn: parent visible: bgImg.status === Image.Error Rectangle { width: 550 height: 400 radius: Appearance.rounding.windowRounding color: "transparent" anchors.centerIn: parent ColumnLayout { anchors.centerIn: parent anchors.margins: Metrics.margin("normal") spacing: Metrics.margin("small") MaterialSymbol { text: "wallpaper" font.pixelSize: Metrics.fontSize("wildass") color: Appearance.colors.colOnLayer2 Layout.alignment: Qt.AlignHCenter } StyledText { text: "Wallpaper Missing" font.pixelSize: Metrics.fontSize("hugeass") font.bold: true color: Appearance.colors.colOnLayer2 horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter } StyledText { text: "Seems like you haven't set a wallpaper yet." font.pixelSize: Metrics.fontSize("small") color: Appearance.colors.colSubtext horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap Layout.alignment: Qt.AlignHCenter } Item { Layout.fillHeight: true } StyledButton { text: "Set wallpaper" icon: "wallpaper" secondary: true radius: Metrics.radius("large") Layout.alignment: Qt.AlignHCenter onClicked: wallpaperProc.running = true } } } } } IpcHandler { target: "background" function change() { wallpaperProc.running = true } function next() { WallpaperSlideshow.nextWallpaper() } } } } Clock { id: clock } }