pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.widgets.widgetCanvas import qs.modules.common.functions as CF import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs.modules.ii.background.widgets import qs.modules.ii.background.widgets.clock import qs.modules.ii.background.widgets.weather Variants { id: root model: Quickshell.screens PanelWindow { id: bgRoot required property var modelData // Hide when fullscreen property list workspacesForMonitor: Hyprland.workspaces.values.filter(workspace => workspace.monitor && workspace.monitor.name == monitor.name) property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace => ((workspace.toplevels.values.filter(window => window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0] visible: GlobalStates.screenLocked || (!(activeWorkspaceWithFullscreen != undefined)) || !Config?.options.background.hideWhenFullscreen // Workspaces property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) property list relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor?.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id) property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1 property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10 property int totalWorkspaces: Config?.options.bar.workspaces.shown ?? 10 // Wallpaper property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov") property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath property bool wallpaperSafetyTriggered: { const enabled = Config.options.workSafety.enable.wallpaper; const sensitiveWallpaper = (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.workSafety.triggerCondition.fileKeywords)); const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)); return enabled && sensitiveWallpaper && sensitiveNetwork; } readonly property real parallaxRation: 1.2 readonly property real additionalScaleFactor: Config.options.background.parallax.workspaceZoom property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated property real scaledWallpaperWidth: wallpaperWidth * effectiveWallpaperScale property real scaledWallpaperHeight: wallpaperHeight * effectiveWallpaperScale property real parallaxTotalPixelsX: Math.max(screen.width - scaledWallpaperWidth, scaledWallpaperWidth - screen.width) property real parallaxTotalPixelsY: Math.max(screen.height - scaledWallpaperHeight, scaledWallpaperHeight - screen.height) readonly property bool verticalParallax: (Config.options.background.parallax.autoVertical && wallpaperHeight > wallpaperWidth) || Config.options.background.parallax.vertical // Colors property bool shouldBlur: (GlobalStates.screenLocked && Config.options.lock.blur.enable) property color dominantColor: Appearance.colors.colPrimary // Default, to be changed property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 property color colText: { if (wallpaperSafetyTriggered) return CF.ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colPrimary, 0.75); return (GlobalStates.screenLocked && shouldBlur) ? Appearance.colors.colOnLayer0 : CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12)); } Behavior on colText { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } // Layer props screen: modelData exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: (GlobalStates.screenLocked && !scaleAnim.running) ? WlrLayer.Overlay : WlrLayer.Bottom // WlrLayershell.layer: WlrLayer.Bottom WlrLayershell.namespace: "quickshell:background" anchors { top: true bottom: true left: true right: true } color: { if (!bgRoot.wallpaperSafetyTriggered || bgRoot.wallpaperIsVideo) return "transparent"; return CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75); } Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } onWallpaperPathChanged: { bgRoot.updateZoomScale(); // Clock position gets updated after zoom scale is updated } // Wallpaper zoom scale function updateZoomScale() { getWallpaperSizeProc.path = bgRoot.wallpaperPath; getWallpaperSizeProc.running = true; } Process { id: getWallpaperSizeProc property string path: bgRoot.wallpaperPath command: ["magick", "identify", "-format", "%w %h", path] stdout: StdioCollector { id: wallpaperSizeOutputCollector onStreamFinished: { const output = wallpaperSizeOutputCollector.text; const [width, height] = output.split(" ").map(Number); const [screenWidth, screenHeight] = [bgRoot.screen.width, bgRoot.screen.height]; bgRoot.wallpaperWidth = width; bgRoot.wallpaperHeight = height; // Perfect image; scale = 1 // Small picture; scale > 1; will zoom in the picture // Big picture; scale < 1; will zoom out the picture // Choose max number so every side will fit const minSuitableScale = Math.max(screenWidth / width, screenHeight / height); bgRoot.effectiveWallpaperScale = minSuitableScale * bgRoot.additionalScaleFactor * bgRoot.parallaxRation; } } } Item { anchors.fill: parent clip: true // Wallpaper StyledImage { id: wallpaper visible: opacity > 0 && !blurLoader.active opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0 cache: false smooth: false property int workspaceIndex: (bgRoot.monitor.activeWorkspace?.id ?? 1) - 1 property real middleFraction: 0.5 property real fraction: { // 0 - start of the picture // 1 - end of the picture if (bgRoot.totalWorkspaces <= 1) { return middleFraction; } return Math.max(0, Math.min(1, workspaceIndex / (bgRoot.totalWorkspaces - 1))); } x: { if (bgRoot.screen.width > bgRoot.scaledWallpaperWidth) { // Center the picture return bgRoot.parallaxTotalPixelsX / 2; } let usedFraction = middleFraction; if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) { usedFraction = fraction; } if (Config.options.background.parallax.enableSidebar) { let sidebarFraction = bgRoot.parallaxRation / 10; usedFraction += (sidebarFraction * GlobalStates.sidebarRightOpen - sidebarFraction * GlobalStates.sidebarLeftOpen); } usedFraction = Math.max(0, Math.min(1, usedFraction)); return - bgRoot.parallaxTotalPixelsX * usedFraction; } y: { if (bgRoot.screen.height > bgRoot.scaledWallpaperHeight) { // Center the picture return bgRoot.parallaxTotalPixelsY / 2; } let usedFraction = middleFraction; if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) { usedFraction = fraction; } usedFraction = Math.max(0, Math.min(1, usedFraction)); return - bgRoot.parallaxTotalPixelsY * usedFraction; } source: bgRoot.wallpaperSafetyTriggered ? "" : bgRoot.wallpaperPath fillMode: Image.PreserveAspectCrop Behavior on x { NumberAnimation { duration: 600 easing.type: Easing.OutCubic } } Behavior on y { NumberAnimation { duration: 600 easing.type: Easing.OutCubic } } sourceSize { width: bgRoot.scaledWallpaperWidth height: bgRoot.scaledWallpaperHeight } width: bgRoot.scaledWallpaperWidth height: bgRoot.scaledWallpaperHeight } Loader { id: blurLoader active: Config.options.lock.blur.enable && (GlobalStates.screenLocked || scaleAnim.running) anchors.fill: wallpaper scale: GlobalStates.screenLocked ? Config.options.lock.blur.extraZoom : 1 Behavior on scale { NumberAnimation { id: scaleAnim duration: 400 easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial } } sourceComponent: GaussianBlur { source: wallpaper radius: GlobalStates.screenLocked ? Config.options.lock.blur.radius : 0 samples: radius * 2 + 1 Rectangle { opacity: GlobalStates.screenLocked ? 1 : 0 anchors.fill: parent color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7) } } } WidgetCanvas { id: widgetCanvas anchors { left: bgRoot.screen.left right: bgRoot.screen.right top: bgRoot.screen.top bottom: bgRoot.screen.bottom horizontalCenter: undefined verticalCenter: undefined readonly property real parallaxFactor: Config.options.background.parallax.widgetsFactor leftMargin: { return bgRoot.screen.width * 0.2; } topMargin: { return bgRoot.screen.height * 0.2; } Behavior on leftMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on topMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } width: wallpaper.width height: wallpaper.height states: State { name: "centered" when: GlobalStates.screenLocked || bgRoot.wallpaperSafetyTriggered PropertyChanges { target: widgetCanvas width: parent.width height: parent.height } AnchorChanges { target: widgetCanvas anchors { left: undefined right: undefined top: undefined bottom: undefined horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } } } transitions: Transition { PropertyAnimation { properties: "width,height" duration: Appearance.animation.elementMove.duration easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } AnchorAnimation { duration: Appearance.animation.elementMove.duration easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } FadeLoader { shown: Config.options.background.widgets.weather.enable sourceComponent: WeatherWidget { screenWidth: bgRoot.screen.width screenHeight: bgRoot.screen.height scaledScreenWidth: bgRoot.screen.width scaledScreenHeight: bgRoot.screen.height wallpaperScale: 1 } } FadeLoader { shown: Config.options.background.widgets.clock.enable sourceComponent: ClockWidget { screenWidth: bgRoot.screen.width screenHeight: bgRoot.screen.height scaledScreenWidth: bgRoot.screen.width scaledScreenHeight: bgRoot.screen.height wallpaperScale: 1 wallpaperSafetyTriggered: bgRoot.wallpaperSafetyTriggered } } } } } }