From e79c163dd91960fa053c02a7d1880c27b8ca7e9f Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 08:33:40 -0400 Subject: [PATCH 01/14] Wallpaper rework - removed swww to the code is easier to maintain - basic multi monitor wallpaper support --- Commons/Settings.qml | 42 +- Modules/Background/Background.qml | 16 +- Modules/Background/Overview.qml | 5 +- Modules/SettingsPanel/Tabs/DisplayTab.qml | 16 +- .../Tabs/WallpaperSelectorTab.qml | 25 +- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 525 +++++------------- README.md | 14 - Services/ScalingService.qml | 37 +- Services/WallpaperService.qml | 188 ++++--- Widgets/NLabel.qml | 8 +- Widgets/NTextInput.qml | 4 + Widgets/NWidgetLoader.qml | 2 +- 12 files changed, 330 insertions(+), 552 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 8f4b74b..f7a967c 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -94,10 +94,10 @@ Singleton { // And not on every reload if (!isLoaded) { Logger.log("Settings", "JSON completed loading") - if (adapter.wallpaper.current !== "") { - Logger.log("Settings", "Set current wallpaper", adapter.wallpaper.current) - WallpaperService.setCurrentWallpaper(adapter.wallpaper.current, true) - } + // if (adapter.wallpaper.current !== "") { + // Logger.log("Settings", "Set current wallpaper", adapter.wallpaper.current) + // WallpaperService.setCurrentWallpaper(adapter.wallpaper.current, true) + // } // Validate monitor configurations, only once // if none of the configured monitors exist, clear the lists @@ -171,22 +171,13 @@ Singleton { // wallpaper property JsonObject wallpaper: JsonObject { property string directory: "/usr/share/wallpapers" - property string current: "" - property bool isRandom: false - property int randomInterval: 300 - property JsonObject swww - - onDirectoryChanged: WallpaperService.listWallpapers() - onIsRandomChanged: WallpaperService.toggleRandomWallpaper() - onRandomIntervalChanged: WallpaperService.restartRandomWallpaperTimer() - - swww: JsonObject { - property bool enabled: false - property string resizeMethod: "crop" - property int transitionFps: 60 - property string transitionType: "random" - property real transitionDuration: 1.1 - } + property bool enableMultiMonitorDirectories: false + property bool setWallpaperOnAllMonitors: true + property bool randomEnabled: false + property int randomIntervalSec: 300 // 5 min + property int transitionDuration: 1500 // 1500 ms + property string transitionType: "fade" + property list monitors: [] } // applauncher @@ -234,19 +225,10 @@ Singleton { property string fontDefault: "Roboto" // Default font for all text property string fontFixed: "DejaVu Sans Mono" // Fixed width font for terminal property string fontBillboard: "Inter" // Large bold font for clocks and prominent displays - - // Legacy compatibility - property string fontFamily: fontDefault // Keep for backward compatibility - - // Idle inhibitor state + property list monitorsScaling: [] property bool idleInhibitorEnabled: false } - // Scaling (not stored inside JsonObject, or it crashes) - property var monitorsScaling: { - - } - // brightness property JsonObject brightness: JsonObject { property int brightnessStep: 5 diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 54a7878..51b0e3e 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -5,26 +5,16 @@ import qs.Commons import qs.Services Loader { - active: !Settings.data.wallpaper.swww.enabled + active: true sourceComponent: Variants { model: Quickshell.screens delegate: PanelWindow { required property ShellScreen modelData - property string wallpaperSource: WallpaperService.currentWallpaper !== "" - && !Settings.data.wallpaper.swww.enabled ? WallpaperService.currentWallpaper : "" + property string wallpaperSource: WallpaperService.getWallpaper(modelData.name) - visible: wallpaperSource !== "" && !Settings.data.wallpaper.swww.enabled - - // Force update when SWWW setting changes - onVisibleChanged: { - if (visible) { - - } else { - - } - } + visible: wallpaperSource !== "" color: Color.transparent screen: modelData WlrLayershell.layer: WlrLayer.Background diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index e673663..19b08e6 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -20,10 +20,9 @@ Loader { delegate: PanelWindow { required property ShellScreen modelData - property string wallpaperSource: WallpaperService.currentWallpaper !== "" - && !Settings.data.wallpaper.swww.enabled ? WallpaperService.currentWallpaper : "" + property string wallpaperSource: WallpaperService.getWallpaper(modelData.name) - visible: wallpaperSource !== "" && !Settings.data.wallpaper.swww.enabled + visible: wallpaperSource !== "" color: Color.transparent screen: modelData WlrLayershell.layer: WlrLayer.Background diff --git a/Modules/SettingsPanel/Tabs/DisplayTab.qml b/Modules/SettingsPanel/Tabs/DisplayTab.qml index 5dfcbfe..a81a361 100644 --- a/Modules/SettingsPanel/Tabs/DisplayTab.qml +++ b/Modules/SettingsPanel/Tabs/DisplayTab.qml @@ -188,7 +188,7 @@ ColumnLayout { } NText { - text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%` + text: `${Math.round(ScalingService.getMonitorScale(modelData.name) * 100)}%` Layout.alignment: Qt.AlignVCenter Layout.minimumWidth: 50 * scaling horizontalAlignment: Text.AlignRight @@ -204,12 +204,8 @@ ColumnLayout { from: 0.7 to: 1.8 stepSize: 0.01 - value: ScalingService.scaleByName(modelData.name) - onPressedChanged: { - var data = Settings.data.monitorsScaling || {} - data[modelData.name] = value - Settings.data.monitorsScaling = data - } + value: ScalingService.getMonitorScale(modelData.name) + onPressedChanged: ScalingService.setMonitorScale(modelData.name, value) Layout.fillWidth: true Layout.minimumWidth: 150 * scaling } @@ -217,11 +213,7 @@ ColumnLayout { NIconButton { icon: "refresh" tooltipText: "Reset Scaling" - onClicked: { - var data = Settings.data.monitorsScaling || {} - data[modelData.name] = 1.0 - Settings.data.monitorsScaling = data - } + onClicked: ScalingService.setMonitorScale(modelData.name, 1.0) } } } diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index 0fa0338..7d34690 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -29,7 +29,7 @@ ColumnLayout { id: currentWallpaperImage anchors.fill: parent anchors.margins: Style.marginXS * scaling - imagePath: WallpaperService.currentWallpaper + imagePath: WallpaperService.getWallpaper(screen.name) fallbackIcon: "image" imageRadius: Style.radiusM * scaling } @@ -62,14 +62,6 @@ ColumnLayout { wrapMode: Text.WordWrap Layout.fillWidth: true } - - NText { - text: Settings.data.wallpaper.swww.enabled ? "Wallpapers will change with " + Settings.data.wallpaper.swww.transitionType - + " transition." : "Wallpapers will change instantly." - color: Color.mOnSurface - font.pointSize: Style.fontSizeXS * scaling - visible: Settings.data.wallpaper.swww.enabled - } } NIconButton { @@ -78,10 +70,17 @@ ColumnLayout { onClicked: { WallpaperService.listWallpapers() } - Layout.alignment: Qt.AlignTop | Qt.AlignRight + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } } + NToggle { + label: "Assign selection to all monitors" + description: "Set selected wallpaper on all monitors at once." + checked: Settings.data.wallpaper.setWallpaperOnAllMonitors + onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked + } + // Wallpaper grid container Item { Layout.fillWidth: true @@ -179,7 +178,11 @@ ColumnLayout { acceptedButtons: Qt.LeftButton hoverEnabled: true onClicked: { - WallpaperService.changeWallpaper(wallpaperPath) + if (Settings.data.wallpaper.setWallpaperOnAllMonitors) { + WallpaperService.changeWallpaper(undefined, wallpaperPath) + } else { + WallpaperService.changeWallpaper(screen.name, wallpaperPath) + } } } } diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index e96b5ef..bd87933 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell import Quickshell.Io import qs.Commons import qs.Services @@ -9,36 +10,59 @@ import qs.Widgets ColumnLayout { id: root - // Process to check if swww is installed - Process { - id: swwwCheck - command: ["which", "swww"] - running: false + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + NTextInput { + label: "Wallpaper Directory" + description: "Path to your wallpaper directory." + text: Settings.data.wallpaper.directory + onEditingFinished: { + Settings.data.wallpaper.directory = text + } + Layout.maximumWidth: 420 * scaling + } - onExited: function (exitCode) { - if (exitCode === 0) { - // SWWW exists, enable it - Settings.data.wallpaper.swww.enabled = true - WallpaperService.startSWWWDaemon() - ToastService.showNotice("Swww", "Enabled") - } else { - // SWWW not found - ToastService.showWarning("Swww", "Not installed") + // Monitor-specific directories + NToggle { + label: "Monitor-specific directories" + description: "Enable multi-monitor wallpaper directory management." + checked: Settings.data.wallpaper.enableMultiMonitorDirectories + onToggled: checked => Settings.data.wallpaper.enableMultiMonitorDirectories = checked + } + + ColumnLayout { + visible: Settings.data.wallpaper.enableMultiMonitorDirectories + spacing: Style.marginL * scaling + Repeater { + model: Quickshell.screens || [] + delegate: Rectangle { + Layout.fillWidth: true + Layout.minimumWidth: 550 * scaling + radius: Style.radiusM * scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling + + ColumnLayout { + id: contentCol + anchors.fill: parent + anchors.margins: Style.marginL * scaling + spacing: Style.marginXXS * scaling + + NTextInput { + label: (modelData.name || "Unknown") + description: `Path to your wallpaper directory for "${(modelData.name || "Unknown")}" monitor` + text: WallpaperService.getMonitorWallpaperDirectory(modelData.name) + labelColor: Color.mSecondary + onEditingFinished: WallpaperService.setMonitorWallpaperDirectory(modelData.name, text) + Layout.maximumWidth: 420 * scaling + } + } + } } } - - stdout: StdioCollector {} - stderr: StdioCollector {} - } - - NTextInput { - label: "Wallpaper Directory" - description: "Path to your wallpaper directory." - text: Settings.data.wallpaper.directory - onEditingFinished: { - Settings.data.wallpaper.directory = text - } - Layout.maximumWidth: 420 * scaling } NDivider { @@ -62,10 +86,42 @@ ColumnLayout { NToggle { label: "Random Wallpaper" description: "Automatically select random wallpapers from the folder." - checked: Settings.data.wallpaper.isRandom - onToggled: checked => { - Settings.data.wallpaper.isRandom = checked - } + checked: Settings.data.wallpaper.randomEnabled + onToggled: checked => Settings.data.wallpaper.randomEnabled = checked + } + + // Transition Type + NComboBox { + label: "Transition Type" + description: "Animation type when switching between wallpapers." + model: WallpaperService.transitionsModel + currentKey: Settings.data.wallpaper.transitionType + onSelected: key => Settings.data.wallpaper.transitionType = key + } + + // Transition Duration + ColumnLayout { + NLabel { + label: "Transition Duration" + description: "Duration of transition animations in seconds." + } + + RowLayout { + spacing: Style.marginL * scaling + NSlider { + Layout.fillWidth: true + from: 100 + to: 10000 + stepSize: 100 + value: Settings.data.wallpaper.transitionDuration + onMoved: Settings.data.wallpaper.transitionDuration = value + cutoutColor: Color.mSurface + } + NText { + text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(2) + "s" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } + } } // Interval (slider + H:M inputs) @@ -79,25 +135,37 @@ ColumnLayout { NText { // Show friendly H:MM format from current settings - text: Time.formatVagueHumanReadableDuration(Settings.data.wallpaper.randomInterval) + text: Time.formatVagueHumanReadableDuration(Settings.data.wallpaper.randomIntervalSec) Layout.alignment: Qt.AlignBottom | Qt.AlignRight } } - // Preset chips + // Preset chips using Repeater RowLayout { id: presetRow spacing: Style.marginS * scaling - // Preset seconds list - property var presets: [15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60] + // Factorized presets data + property var intervalPresets: [ + 5 * 60, + 10 * 60, + 15 * 60, + 30 * 60, + 45 * 60, + 60 * 60, + 90 * 60, + 120 * 60, + ] + // Whether current interval equals one of the presets - property bool isCurrentPreset: presets.indexOf(Settings.data.wallpaper.randomInterval) !== -1 + property bool isCurrentPreset: { + return intervalPresets.some(seconds => seconds === Settings.data.wallpaper.randomIntervalSec) + } // Allow user to force open the custom input; otherwise it's auto-open when not a preset property bool customForcedVisible: false function setIntervalSeconds(sec) { - Settings.data.wallpaper.randomInterval = sec + Settings.data.wallpaper.randomIntervalSec = sec WallpaperService.restartRandomWallpaperTimer() // Hide custom when selecting a preset customForcedVisible = false @@ -105,168 +173,25 @@ ColumnLayout { // Helper to color selected chip function isSelected(sec) { - return Settings.data.wallpaper.randomInterval === sec + return Settings.data.wallpaper.randomIntervalSec === sec } - // 15m - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(15 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label15.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(15 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(15 * 60) - } - NText { - id: label15 - anchors.centerIn: parent - text: "15m" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(15 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 30m - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(30 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label30.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(30 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(30 * 60) - } - NText { - id: label30 - anchors.centerIn: parent - text: "30m" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(30 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 45m - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(45 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label45.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(45 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(45 * 60) - } - NText { - id: label45 - anchors.centerIn: parent - text: "45m" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(45 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 1h - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(60 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label1h.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(60 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(60 * 60) - } - NText { - id: label1h - anchors.centerIn: parent - text: "1h" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(60 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 1h 30m - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(90 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label90.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(90 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(90 * 60) - } - NText { - id: label90 - anchors.centerIn: parent - text: "1h 30m" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(90 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 2h - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(120 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label2h.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(120 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(120 * 60) - } - NText { - id: label2h - anchors.centerIn: parent - text: "2h" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(120 * 60) ? Color.mOnPrimary : Color.mOnSurface + // Repeater for preset chips + Repeater { + model: presetRow.intervalPresets + delegate: IntervalPresetChip { + seconds: modelData + label: Time.formatVagueHumanReadableDuration(modelData) + selected: presetRow.isSelected(modelData) + onClicked: presetRow.setIntervalSeconds(modelData) } } // Custom… opens inline input - Rectangle { - radius: height * 0.5 - color: customRow.visible ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: labelCustom.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: customRow.visible ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.customForcedVisible = !presetRow.customForcedVisible - } - NText { - id: labelCustom - anchors.centerIn: parent - text: customRow.visible ? "Custom" : "Custom…" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: customRow.visible ? Color.mOnPrimary : Color.mOnSurface - } + IntervalPresetChip { + label: customRow.visible ? "Custom" : "Custom…" + selected: customRow.visible + onClicked: presetRow.customForcedVisible = !presetRow.customForcedVisible } } @@ -282,12 +207,11 @@ ColumnLayout { description: "Enter time as HH:MM (e.g., 01:30)." inputMaxWidth: 100 * scaling text: { - const s = Settings.data.wallpaper.randomInterval + const s = Settings.data.wallpaper.randomIntervalSec const h = Math.floor(s / 3600) const m = Math.floor((s % 3600) / 60) return h + ":" + (m < 10 ? ("0" + m) : m) } - onEditingFinished: { const m = text.trim().match(/^(\d{1,2}):(\d{2})$/) if (m) { @@ -297,7 +221,7 @@ ColumnLayout { return h = Math.max(0, Math.min(24, h)) min = Math.max(0, Math.min(59, min)) - Settings.data.wallpaper.randomInterval = (h * 3600) + (min * 60) + Settings.data.wallpaper.randomIntervalSec = (h * 3600) + (min * 60) WallpaperService.restartRandomWallpaperTimer() // Keep custom visible after manual entry presetRow.customForcedVisible = true @@ -308,193 +232,32 @@ ColumnLayout { } } - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } + // Reusable component for interval preset chips + component IntervalPresetChip: Rectangle { + property int seconds: 0 + property string label: "" + property bool selected: false + signal clicked() - // ------------------------------- - // Swww - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true + radius: height * 0.5 + color: selected ? Color.mPrimary : Color.mSurfaceVariant + implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) + implicitWidth: chipLabel.implicitWidth + Style.marginM * 1.5 * scaling + border.width: 1 + border.color: selected ? Color.transparent : Color.mOutline + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: parent.clicked() + } NText { - text: "Swww" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - } - - // Use SWWW - NToggle { - label: "Use Swww" - description: "Use Swww daemon for advanced wallpaper management." - checked: Settings.data.wallpaper.swww.enabled - onToggled: checked => { - if (checked) { - // Check if swww is installed - swwwCheck.running = true - } else { - Settings.data.wallpaper.swww.enabled = false - ToastService.showNotice("Swww", "Disabled") - } - } - } - - // SWWW Settings (only visible when useSWWW is enabled) - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginS * scaling - visible: Settings.data.wallpaper.swww.enabled - - // Resize Mode - NComboBox { - label: "Resize Mode" - description: "How Swww should resize wallpapers to fit the screen." - model: ListModel { - ListElement { - key: "no" - name: "No" - } - ListElement { - key: "crop" - name: "Crop" - } - ListElement { - key: "fit" - name: "Fit" - } - ListElement { - key: "stretch" - name: "Stretch" - } - } - currentKey: Settings.data.wallpaper.swww.resizeMethod - onSelected: key => { - Settings.data.wallpaper.swww.resizeMethod = key - } - } - - // Transition Type - NComboBox { - label: "Transition Type" - description: "Animation type when switching between wallpapers." - model: ListModel { - ListElement { - key: "none" - name: "None" - } - ListElement { - key: "simple" - name: "Simple" - } - ListElement { - key: "fade" - name: "Fade" - } - ListElement { - key: "left" - name: "Left" - } - ListElement { - key: "right" - name: "Right" - } - ListElement { - key: "top" - name: "Top" - } - ListElement { - key: "bottom" - name: "Bottom" - } - ListElement { - key: "wipe" - name: "Wipe" - } - ListElement { - key: "wave" - name: "Wave" - } - ListElement { - key: "grow" - name: "Grow" - } - ListElement { - key: "center" - name: "Center" - } - ListElement { - key: "any" - name: "Any" - } - ListElement { - key: "outer" - name: "Outer" - } - ListElement { - key: "random" - name: "Random" - } - } - currentKey: Settings.data.wallpaper.swww.transitionType - onSelected: key => { - Settings.data.wallpaper.swww.transitionType = key - } - } - - // Transition FPS - ColumnLayout { - NLabel { - label: "Transition FPS" - description: "Frames per second for transition animations." - } - - RowLayout { - spacing: Style.marginL * scaling - NSlider { - Layout.fillWidth: true - from: 30 - to: 500 - stepSize: 5 - value: Settings.data.wallpaper.swww.transitionFps - onMoved: Settings.data.wallpaper.swww.transitionFps = Math.round(value) - cutoutColor: Color.mSurface - } - NText { - text: Settings.data.wallpaper.swww.transitionFps + " FPS" - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } - } - } - - // Transition Duration - ColumnLayout { - NLabel { - label: "Transition Duration" - description: "Duration of transition animations in seconds." - } - - RowLayout { - spacing: Style.marginL * scaling - NSlider { - Layout.fillWidth: true - from: 0.25 - to: 10 - stepSize: 0.05 - value: Settings.data.wallpaper.swww.transitionDuration - onMoved: Settings.data.wallpaper.swww.transitionDuration = value - cutoutColor: Color.mSurface - } - NText { - text: Settings.data.wallpaper.swww.transitionDuration.toFixed(2) + "s" - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } - } - } + id: chipLabel + anchors.centerIn: parent + text: parent.label + font.pointSize: Style.fontSizeS * scaling + color: parent.selected ? Color.mOnPrimary : Color.mOnSurface } } @@ -503,4 +266,4 @@ ColumnLayout { Layout.topMargin: Style.marginXL * scaling Layout.bottomMargin: Style.marginXL * scaling } -} +} \ No newline at end of file diff --git a/README.md b/README.md index b7d81b2..003be31 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,6 @@ Features a modern modular architecture with a status bar, notification system, c ### Optional - `cliphist` - For clipboard history support -- `swww` - Wallpaper animations and effects - `matugen` - Material You color scheme generation - `cava` - Audio visualizer component - `wlsunset` - To be able to use NightLight @@ -270,14 +269,6 @@ The launcher supports special commands for enhanced functionality: ## Advanced Configuration -### Niri Configuration - -Add this to your `layout` section for proper swww integration: - -``` -background-color "transparent" -``` - ### Recommended Compositor Settings For Niri: @@ -288,11 +279,6 @@ window-rule { clip-to-geometry true } -layer-rule { - match namespace="^swww-daemon$" - place-within-backdrop true -} - layer-rule { match namespace="^quickshell-wallpaper$" } diff --git a/Services/ScalingService.qml b/Services/ScalingService.qml index 2b6cc58..8aa32bb 100644 --- a/Services/ScalingService.qml +++ b/Services/ScalingService.qml @@ -11,7 +11,7 @@ Singleton { function scale(aScreen) { try { if (aScreen !== undefined && aScreen.name !== undefined) { - return scaleByName(aScreen.name) + return getMonitorScale(aScreen.name) } } catch (e) { @@ -20,21 +20,46 @@ Singleton { return 1.0 } - function scaleByName(aScreenName) { + // ------------------------------------------- + function getMonitorScale(aScreenName) { try { - if (Settings.data.monitorsScaling !== undefined) { - if (Settings.data.monitorsScaling[aScreenName] !== undefined) { - return Settings.data.monitorsScaling[aScreenName] + var monitors = Settings.data.ui.monitorsScaling + if (monitors !== undefined) { + for (var i = 0; i < monitors.length; i++) { + if (monitors[i].name !== undefined && monitors[i].name === aScreenName) { + return monitors[i].scale + } } } } catch (e) { //Logger.warn(e) } - return 1.0 } + // ------------------------------------------- + function setMonitorScale(aScreenName, scale) { + try { + var monitors = Settings.data.ui.monitorsScaling + if (monitors !== undefined) { + for (var i = 0; i < monitors.length; i++) { + if (monitors[i].name !== undefined && monitors[i].name === aScreenName) { + monitors[i].scale = scale + return + } + } + } + monitors.push({ + "name": aScreenName, + "scale": scale + }) + } catch (e) { + + //Logger.warn(e) + } + } + // ------------------------------------------- // Dynamic scaling based on resolution diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 0b43142..481d4c5 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -10,66 +10,138 @@ Singleton { id: root Component.onCompleted: { - Logger.log("Wallpapers", "Service started") + Logger.log("Wallpaper", "Service started") listWallpapers() // Wallpaper is set when the settings are loaded. // Don't start random wallpaper during initialization } + readonly property ListModel transitionsModel: ListModel { + ListElement { + key: "none" + name: "None" + } + ListElement { + key: "fade" + name: "Fade" + } + } + property var wallpaperList: [] - property string currentWallpaper: Settings.data.wallpaper.current property bool scanning: false - // SWWW - property string transitionType: Settings.data.wallpaper.swww.transitionType - property var randomChoices: ["simple", "fade", "left", "right", "top", "bottom", "wipe", "wave", "grow", "center", "any", "outer"] + Connections { + target: Settings.data.wallpaper + onDirectoryChanged: WallpaperService.listWallpapers() + onRandomEnabledChanged: WallpaperService.toggleRandomWallpaper() + onRandomIntervalChanged: WallpaperService.restartRandomWallpaperTimer() + } + // ------------------------------------------------------------------- + function geMonitorDefinition(screenName) { + var monitors = Settings.data.wallpaper.monitors + if (monitors !== undefined) { + for (var i = 0; i < monitors.length; i++) { + if (monitors[i].name !== undefined && monitors[i].name === screenName) { + return monitors[i] + } + } + } + } + + // ------------------------------------------------------------------- + function getMonitorWallpaperDirectory(screenName) { + var monitor = geMonitorDefinition(screenName) + if (monitor !== undefined) { + return monitor.directory + } + return Settings.data.wallpaper.directory + } + + // ------------------------------------------------------------------- + function setMonitorWallpaperDirectory(screenName, directory) { + var monitor = geMonitorDefinition(screenName) + if (monitor !== undefined) { + monitor.directory = directory + return + } + + Settings.data.wallpaper.monitors.push({ + "name": screenName, + "directory": directory, + "wallpaper": "" + }) + } + + // ------------------------------------------------------------------- function listWallpapers() { - Logger.log("Wallpapers", "Listing wallpapers") + Logger.log("Wallpaper", "Listing wallpapers") scanning = true wallpaperList = [] // Set the folder directly to avoid model reset issues folderModel.folder = "file://" + (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "") } - function changeWallpaper(path) { - Logger.log("Wallpapers", "Changing to:", path) - setCurrentWallpaper(path, false) + // ------------------------------------------------------------------- + function getWallpaper(screenName) { + // Logger.log("Wallpaper", "getWallpaper on", screenName) + var monitor = geMonitorDefinition(screenName) + if (monitor !== undefined) { + return monitor["wallpaper"] + } + return "" } - function setCurrentWallpaper(path, isInitial) { - // Only regenerate colors if the wallpaper actually changed - var wallpaperChanged = currentWallpaper !== path - - currentWallpaper = path - if (!isInitial) { - Settings.data.wallpaper.current = path - } - if (Settings.data.wallpaper.swww.enabled) { - if (Settings.data.wallpaper.swww.transitionType === "random") { - transitionType = randomChoices[Math.floor(Math.random() * randomChoices.length)] - } else { - transitionType = Settings.data.wallpaper.swww.transitionType - } - - changeWallpaperProcess.running = true + // ------------------------------------------------------------------- + function changeWallpaper(screenName, path) { + if (screenName !== undefined) { + setCurrentWallpaper(screenName, path, false) } else { + for (var i = 0; i < Quickshell.screens.length; i++) { + setCurrentWallpaper(Quickshell.screens[i].name, path, false) + } + } + } - // Fallback: update the settings directly for non-SWWW mode - //Logger.log("Wallpapers", "Not using Swww, setting wallpaper directly") + // ------------------------------------------------------------------- + function setCurrentWallpaper(screenName, path, isInitial) { + if (screenName === undefined) { + Logger.warn("Wallpaper", "setCurrentWallpaper", "no screen specified") + return } + Logger.log("Wallpaper", "setCurrentWallpaper on", screenName, ": ", path) + + var monitor = geMonitorDefinition(screenName) + if (monitor !== undefined) { + monitor["wallpaper"] = path + } else { + Settings.data.wallpaper.monitors.push({ + "name": screenName, + "directory": Settings.data.wallpaper.directory, + "wallpaper": path + }) + } + + // // Only regenerate colors if the wallpaper actually changed + // var wallpaperChanged = currentWallpaper !== path + + // currentWallpaper = path + // if (!isInitial) { + // Settings.data.wallpaper.current = path + // } if (randomWallpaperTimer.running) { randomWallpaperTimer.restart() } // Only notify ColorScheme service if the wallpaper actually changed - if (wallpaperChanged) { - ColorSchemeService.changedWallpaper() - } + // if (wallpaperChanged) { + // ColorSchemeService.changedWallpaper() + // } } + // ------------------------------------------------------------------- function setRandomWallpaper() { var randomIndex = Math.floor(Math.random() * wallpaperList.length) var randomPath = wallpaperList[randomIndex] @@ -79,6 +151,7 @@ Singleton { setCurrentWallpaper(randomPath, false) } + // ------------------------------------------------------------------- function toggleRandomWallpaper() { if (Settings.data.wallpaper.isRandom && !randomWallpaperTimer.running) { randomWallpaperTimer.start() @@ -88,6 +161,7 @@ Singleton { } } + // ------------------------------------------------------------------- function restartRandomWallpaperTimer() { if (Settings.data.wallpaper.isRandom) { randomWallpaperTimer.stop() @@ -95,16 +169,12 @@ Singleton { } } - function startSWWWDaemon() { - if (Settings.data.wallpaper.swww.enabled) { - Logger.log("Swww", "Requesting swww-daemon") - startDaemonProcess.running = true - } - } - + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- Timer { id: randomWallpaperTimer - interval: Settings.data.wallpaper.randomInterval * 1000 + interval: Settings.data.wallpaper.randomIntervalSec * 1000 running: false repeat: true onTriggered: setRandomWallpaper() @@ -113,7 +183,6 @@ Singleton { FolderListModel { id: folderModel - // Swww supports many images format but Quickshell only support a subset of those. nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"] showDirs: false sortField: FolderListModel.Name @@ -127,46 +196,7 @@ Singleton { } wallpaperList = files scanning = false - Logger.log("Wallpapers", "List refreshed, count:", wallpaperList.length) - } - } - } - - Process { - id: changeWallpaperProcess - command: ["swww", "img", "--resize", Settings.data.wallpaper.swww.resizeMethod, "--transition-fps", Settings.data.wallpaper.swww.transitionFps.toString( - ), "--transition-type", transitionType, "--transition-duration", Settings.data.wallpaper.swww.transitionDuration.toString( - ), currentWallpaper] - running: false - - onStarted: { - - } - - onExited: function (exitCode, exitStatus) { - Logger.log("Swww", "Process finished with exit code:", exitCode, "status:", exitStatus) - if (exitCode !== 0) { - Logger.log("Swww", "Process failed. Make sure swww-daemon is running with: swww-daemon") - Logger.log("Swww", "You can start it with: swww-daemon --format xrgb") - } - } - } - - Process { - id: startDaemonProcess - command: ["swww-daemon", "--format", "xrgb"] - running: false - - onStarted: { - Logger.log("Swww", "Daemon start process initiated") - } - - onExited: function (exitCode, exitStatus) { - Logger.log("Swww", "Daemon start process finished with exit code:", exitCode) - if (exitCode === 0) { - Logger.log("Swww", "Daemon started successfully") - } else { - Logger.log("Swww", "Failed to start daemon, may already be running") + Logger.log("Wallpaper", "List refreshed, count:", wallpaperList.length) } } } diff --git a/Widgets/NLabel.qml b/Widgets/NLabel.qml index cf05b8f..e05604c 100644 --- a/Widgets/NLabel.qml +++ b/Widgets/NLabel.qml @@ -3,8 +3,12 @@ import QtQuick.Layouts import qs.Commons ColumnLayout { + id: root + property string label: "" property string description: "" + property color labelColor: Color.mOnSurface + property color descriptionColor: Color.mOnSurfaceVariant spacing: Style.marginXXS * scaling Layout.fillWidth: true @@ -13,14 +17,14 @@ ColumnLayout { text: label font.pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold - color: Color.mOnSurface + color: labelColor visible: label !== "" } NText { text: description font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant + color: descriptionColor wrapMode: Text.WordWrap visible: description !== "" Layout.fillWidth: true diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index 6533af9..cad5077 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -12,6 +12,8 @@ ColumnLayout { property bool readOnly: false property bool enabled: true property int inputMaxWidth: 420 * scaling + property color labelColor: Color.mOnSurface + property color descriptionColor: Color.mOnSurfaceVariant property alias text: input.text property alias placeholderText: input.placeholderText @@ -25,6 +27,8 @@ ColumnLayout { NLabel { label: root.label description: root.description + labelColor: root.labelColor + descriptionColor: root.descriptionColor visible: root.label !== "" || root.description !== "" } diff --git a/Widgets/NWidgetLoader.qml b/Widgets/NWidgetLoader.qml index a9e7ac5..c0a623e 100644 --- a/Widgets/NWidgetLoader.qml +++ b/Widgets/NWidgetLoader.qml @@ -35,7 +35,7 @@ Item { } } } - Logger.log("NWidgetLoader", "Loaded", widgetName, "on screen", item.screen.name) + //Logger.log("NWidgetLoader", "Loaded", widgetName, "on screen", item.screen.name) } } From 861e207fb6f743f71d0c09f9bb27a7a6c70ec874 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 09:55:47 -0400 Subject: [PATCH 02/14] Wip! --- Commons/Settings.qml | 7 +-- Modules/Background/Background.qml | 22 +++---- Modules/Background/Overview.qml | 27 ++++----- Modules/LockScreen/LockScreen.qml | 2 +- .../Tabs/WallpaperSelectorTab.qml | 5 +- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 15 +---- Services/MatugenService.qml | 2 + Services/WallpaperService.qml | 60 ++++++++++--------- flake.nix | 1 - 9 files changed, 59 insertions(+), 82 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index f7a967c..917707b 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -90,14 +90,9 @@ Singleton { } onLoaded: function () { Qt.callLater(function () { - // Some stuff like wallpaper setup and settings validation should just be executed once on startup - // And not on every reload + // Some stuff like settings validation should just be executed once on startup and not on every reload if (!isLoaded) { Logger.log("Settings", "JSON completed loading") - // if (adapter.wallpaper.current !== "") { - // Logger.log("Settings", "Set current wallpaper", adapter.wallpaper.current) - // WallpaperService.setCurrentWallpaper(adapter.wallpaper.current, true) - // } // Validate monitor configurations, only once // if none of the configured monitors exist, clear the lists diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 51b0e3e..f4e9dd4 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -4,17 +4,18 @@ import Quickshell.Wayland import qs.Commons import qs.Services -Loader { - active: true +Variants { + model: Quickshell.screens - sourceComponent: Variants { - model: Quickshell.screens + delegate: Loader { - delegate: PanelWindow { - required property ShellScreen modelData - property string wallpaperSource: WallpaperService.getWallpaper(modelData.name) + required property ShellScreen modelData + property string wallpaperSource: WallpaperService.getWallpaper(modelData.name) + + active: wallpaperSource !== "" + + sourceComponent: PanelWindow { - visible: wallpaperSource !== "" color: Color.transparent screen: modelData WlrLayershell.layer: WlrLayer.Background @@ -28,15 +29,10 @@ Loader { left: true } - margins { - top: 0 - } - Image { anchors.fill: parent fillMode: Image.PreserveAspectCrop source: wallpaperSource - visible: wallpaperSource !== "" cache: true smooth: true mipmap: false diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index 19b08e6..e53f8b5 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -6,23 +6,20 @@ import qs.Commons import qs.Services import qs.Widgets -Loader { - active: CompositorService.isNiri +Variants { + model: Quickshell.screens - Component.onCompleted: { - if (CompositorService.isNiri) { - Logger.log("Overview", "Loading Overview component for Niri") - } - } + delegate: Loader { + required property ShellScreen modelData + property string wallpaperSource: WallpaperService.getWallpaper(modelData.name) - sourceComponent: Variants { - model: Quickshell.screens + active: CompositorService.isNiri && wallpaperSource !== "" - delegate: PanelWindow { - required property ShellScreen modelData - property string wallpaperSource: WallpaperService.getWallpaper(modelData.name) + sourceComponent: PanelWindow { + Component.onCompleted: { + Logger.log("Overview", "Loading Overview component for Niri on", modelData.name) + } - visible: wallpaperSource !== "" color: Color.transparent screen: modelData WlrLayershell.layer: WlrLayer.Background @@ -38,19 +35,15 @@ Loader { Image { id: bgImage - anchors.fill: parent fillMode: Image.PreserveAspectCrop source: wallpaperSource cache: true smooth: true mipmap: false - visible: wallpaperSource !== "" } MultiEffect { - id: overviewBgBlur - anchors.fill: parent source: bgImage blurEnabled: true diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index f155919..131f406 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -93,7 +93,7 @@ Loader { id: lockBgImage anchors.fill: parent fillMode: Image.PreserveAspectCrop - source: WallpaperService.currentWallpaper !== "" ? WallpaperService.currentWallpaper : "" + source: WallpaperService.getWallpaper(screen.name) cache: true smooth: true mipmap: false diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index 7d34690..b49e580 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -23,10 +23,9 @@ ColumnLayout { Layout.fillWidth: true Layout.preferredHeight: 140 * scaling radius: Style.radiusM * scaling - color: Color.mPrimary + color: Color.mSecondary NImageRounded { - id: currentWallpaperImage anchors.fill: parent anchors.margins: Style.marginXS * scaling imagePath: WallpaperService.getWallpaper(screen.name) @@ -113,7 +112,7 @@ ColumnLayout { id: wallpaperItem property string wallpaperPath: modelData - property bool isSelected: wallpaperPath === WallpaperService.currentWallpaper + property bool isSelected: wallpaperPath === WallpaperService.getWallpaper(screen.name) width: wallpaperGridView.itemSize height: Math.floor(wallpaperGridView.itemSize * 0.67) diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index bd87933..fac62cb 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -146,16 +146,7 @@ ColumnLayout { spacing: Style.marginS * scaling // Factorized presets data - property var intervalPresets: [ - 5 * 60, - 10 * 60, - 15 * 60, - 30 * 60, - 45 * 60, - 60 * 60, - 90 * 60, - 120 * 60, - ] + property var intervalPresets: [5 * 60, 10 * 60, 15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60] // Whether current interval equals one of the presets property bool isCurrentPreset: { @@ -237,7 +228,7 @@ ColumnLayout { property int seconds: 0 property string label: "" property bool selected: false - signal clicked() + signal clicked radius: height * 0.5 color: selected ? Color.mPrimary : Color.mSurfaceVariant @@ -266,4 +257,4 @@ ColumnLayout { Layout.topMargin: Style.marginXL * scaling Layout.bottomMargin: Style.marginXL * scaling } -} \ No newline at end of file +} diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml index 1749410..dd8be4a 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -22,6 +22,8 @@ Singleton { // Ensure cache dir exists Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]) + + // TODO: fix matugen var content = buildConfigToml() var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" var wp = WallpaperService.currentWallpaper.replace(/'/g, "'\\''") diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 481d4c5..4ddb792 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -11,10 +11,6 @@ Singleton { Component.onCompleted: { Logger.log("Wallpaper", "Service started") - listWallpapers() - - // Wallpaper is set when the settings are loaded. - // Don't start random wallpaper during initialization } readonly property ListModel transitionsModel: ListModel { @@ -33,9 +29,9 @@ Singleton { Connections { target: Settings.data.wallpaper - onDirectoryChanged: WallpaperService.listWallpapers() - onRandomEnabledChanged: WallpaperService.toggleRandomWallpaper() - onRandomIntervalChanged: WallpaperService.restartRandomWallpaperTimer() + function onDirectoryChanged() { console.log("ondirchanged") ; root.listWallpapers() } + function onRandomEnabledChanged() { root.toggleRandomWallpaper() } + function onRandomIntervalSecChanged() { root.restartRandomWallpaperTimer() } } // ------------------------------------------------------------------- @@ -87,7 +83,7 @@ Singleton { function getWallpaper(screenName) { // Logger.log("Wallpaper", "getWallpaper on", screenName) var monitor = geMonitorDefinition(screenName) - if (monitor !== undefined) { + if ((monitor !== undefined) && (monitor["wallpaper"] !== undefined)) { return monitor["wallpaper"] } return "" @@ -95,8 +91,9 @@ Singleton { // ------------------------------------------------------------------- function changeWallpaper(screenName, path) { + Logger.log("Changing wallpaper") if (screenName !== undefined) { - setCurrentWallpaper(screenName, path, false) + setCurrentWallpaper(screenName, path) } else { for (var i = 0; i < Quickshell.screens.length; i++) { setCurrentWallpaper(Quickshell.screens[i].name, path, false) @@ -105,7 +102,11 @@ Singleton { } // ------------------------------------------------------------------- - function setCurrentWallpaper(screenName, path, isInitial) { + function setCurrentWallpaper(screenName, path) { + if (path === "" || path === undefined) { + return + } + if (screenName === undefined) { Logger.warn("Wallpaper", "setCurrentWallpaper", "no screen specified") return @@ -113,50 +114,51 @@ Singleton { Logger.log("Wallpaper", "setCurrentWallpaper on", screenName, ": ", path) + var wallpaperChanged = false + var monitor = geMonitorDefinition(screenName) if (monitor !== undefined) { + wallpaperChanged = (monitor["wallpaper"] !== path) monitor["wallpaper"] = path } else { + wallpaperChanged = true Settings.data.wallpaper.monitors.push({ "name": screenName, "directory": Settings.data.wallpaper.directory, "wallpaper": path - }) + }) } - // // Only regenerate colors if the wallpaper actually changed - // var wallpaperChanged = currentWallpaper !== path - - // currentWallpaper = path - // if (!isInitial) { - // Settings.data.wallpaper.current = path - // } + // Restart the random wallpaper timer if (randomWallpaperTimer.running) { randomWallpaperTimer.restart() } - // Only notify ColorScheme service if the wallpaper actually changed - // if (wallpaperChanged) { - // ColorSchemeService.changedWallpaper() - // } + // Notify ColorScheme service if the wallpaper actually changed + if (wallpaperChanged) { + ColorSchemeService.changedWallpaper() + } } // ------------------------------------------------------------------- function setRandomWallpaper() { - var randomIndex = Math.floor(Math.random() * wallpaperList.length) - var randomPath = wallpaperList[randomIndex] - if (!randomPath) { - return + Logger.log("Wallpaper", "setRandomWallpaper"); + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name + // TODO one list per monitor + var randomIndex = Math.floor(Math.random() * wallpaperList.length) + var randomPath = wallpaperList[randomIndex] + setCurrentWallpaper(screenName, randomPath) } - setCurrentWallpaper(randomPath, false) } // ------------------------------------------------------------------- function toggleRandomWallpaper() { - if (Settings.data.wallpaper.isRandom && !randomWallpaperTimer.running) { + Logger.log("Wallpaper", "toggleRandomWallpaper") + if (Settings.data.wallpaper.randomEnabled && !randomWallpaperTimer.running) { randomWallpaperTimer.start() setRandomWallpaper() - } else if (!Settings.data.randomWallpaper && randomWallpaperTimer.running) { + } else if (!Settings.data.wallpaper.randomEnabled && randomWallpaperTimer.running) { randomWallpaperTimer.stop() } } diff --git a/flake.nix b/flake.nix index 67aec51..b61342f 100644 --- a/flake.nix +++ b/flake.nix @@ -47,7 +47,6 @@ libnotify matugen networkmanager - swww wl-clipboard ]; From 7c6c908076fb7b15f1133539316475d6f60b94a6 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 12:40:19 -0400 Subject: [PATCH 03/14] Logger: new callStack() method --- Commons/Logger.qml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Commons/Logger.qml b/Commons/Logger.qml index 22a4726..72b62e4 100644 --- a/Commons/Logger.qml +++ b/Commons/Logger.qml @@ -17,6 +17,14 @@ Singleton { } } + function _getStackTrace() { + try { + throw new Error("Stack trace") + } catch (e) { + return e.stack + } + } + function log(...args) { var msg = _formatMessage(...args) console.log(msg) @@ -31,4 +39,20 @@ Singleton { var msg = _formatMessage(...args) console.error(msg) } + + function callStack() { + var stack = _getStackTrace() + Logger.log("Debug", "--------------------------") + Logger.log("Debug", "Current call stack") + // Split the stack into lines and log each one + var stackLines = stack.split('\n') + for (var i = 0; i < stackLines.length; i++) { + var line = stackLines[i].trim() // Remove leading/trailing whitespace + if (line.length > 0) { + // Only log non-empty lines + Logger.log("Debug", `- ${line}`) + } + } + Logger.log("Debug", "--------------------------") + } } From c37ef867a1fdd1bf707eb2d21c75400088d5239e Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 12:41:37 -0400 Subject: [PATCH 04/14] Wallpaper: delay service initialization until settings are ready --- Commons/Settings.qml | 18 ++++++++---------- Modules/Background/Background.qml | 5 ++--- Modules/Background/Overview.qml | 5 ++--- Services/MatugenService.qml | 1 - 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 917707b..c4f11b7 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -89,18 +89,16 @@ Singleton { reload() } onLoaded: function () { - Qt.callLater(function () { - // Some stuff like settings validation should just be executed once on startup and not on every reload - if (!isLoaded) { - Logger.log("Settings", "JSON completed loading") + if (!isLoaded) { + Logger.log("Settings", "----------------------------") + Logger.log("Settings", "Settings loaded successfully") + isLoaded = true - // Validate monitor configurations, only once - // if none of the configured monitors exist, clear the lists + Qt.callLater(function () { + // Some stuff like settings validation should just be executed once on startup and not on every reload validateMonitorConfigurations() - - isLoaded = true - } - }) + }) + } } onLoadFailed: function (error) { if (error.toString().includes("No such file") || error === 2) diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index f4e9dd4..ef6cd39 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -10,9 +10,8 @@ Variants { delegate: Loader { required property ShellScreen modelData - property string wallpaperSource: WallpaperService.getWallpaper(modelData.name) - active: wallpaperSource !== "" + active: Settings.isLoaded && WallpaperService.getWallpaper(modelData.name) sourceComponent: PanelWindow { @@ -32,7 +31,7 @@ Variants { Image { anchors.fill: parent fillMode: Image.PreserveAspectCrop - source: wallpaperSource + source: WallpaperService.getWallpaper(modelData.name) cache: true smooth: true mipmap: false diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index e53f8b5..32237d1 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -11,9 +11,8 @@ Variants { delegate: Loader { required property ShellScreen modelData - property string wallpaperSource: WallpaperService.getWallpaper(modelData.name) - active: CompositorService.isNiri && wallpaperSource !== "" + active: Settings.isLoaded && CompositorService.isNiri sourceComponent: PanelWindow { Component.onCompleted: { @@ -37,7 +36,7 @@ Variants { id: bgImage anchors.fill: parent fillMode: Image.PreserveAspectCrop - source: wallpaperSource + source: WallpaperService.getWallpaper(modelData.name) cache: true smooth: true mipmap: false diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml index dd8be4a..939324b 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -22,7 +22,6 @@ Singleton { // Ensure cache dir exists Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]) - // TODO: fix matugen var content = buildConfigToml() var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" From db3ea7ed73c408ed6bc21e4ade337129f7e2c05c Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 13:04:11 -0400 Subject: [PATCH 05/14] Wallpaper: cleanup --- .../Tabs/WallpaperSelectorTab.qml | 32 +++-- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 2 +- Services/WallpaperService.qml | 132 ++++++++++-------- 3 files changed, 97 insertions(+), 69 deletions(-) diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index b49e580..be415ff 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -78,6 +78,7 @@ ColumnLayout { description: "Set selected wallpaper on all monitors at once." checked: Settings.data.wallpaper.setWallpaperOnAllMonitors onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked + visible: (wallpaperService.wallpaperList.length > 0) } // Wallpaper grid container @@ -187,28 +188,37 @@ ColumnLayout { } } - // Empty state + + } + + // Empty state Rectangle { - anchors.fill: parent color: Color.mSurface radius: Style.radiusM * scaling border.color: Color.mOutline border.width: Math.max(1, Style.borderS * scaling) visible: WallpaperService.wallpaperList.length === 0 && !WallpaperService.scanning + Layout.fillWidth: true + Layout.preferredHeight: 130 * scaling ColumnLayout { - anchors.centerIn: parent - spacing: Style.marginM * scaling + id: fallbackColumn + anchors.fill: parent + anchors.margins: Style.marginL * Scaling + + Item { + Layout.fillHeight: true + } NIcon { text: "folder_open" - font.pointSize: Style.fontSizeL * scaling + font.pointSize: Style.fontSizeXL * scaling color: Color.mOnSurface Layout.alignment: Qt.AlignHCenter } NText { - text: "No wallpapers found" + text: "No wallpaper found." color: Color.mOnSurface font.weight: Style.fontWeightBold Layout.alignment: Qt.AlignHCenter @@ -216,14 +226,16 @@ ColumnLayout { NText { text: "Make sure your wallpaper directory is configured and contains image files." - color: Color.mOnSurface + color: Color.mOnSurfaceVariant wrapMode: Text.WordWrap - horizontalAlignment: Text.AlignHCenter - Layout.preferredWidth: Style.sliderWidth * 1.5 * scaling + Layout.alignment: Qt.AlignHCenter + } + + Item { + Layout.fillHeight: true } } } - } NDivider { Layout.fillWidth: true diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index fac62cb..f1e2b6f 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -54,7 +54,7 @@ ColumnLayout { NTextInput { label: (modelData.name || "Unknown") description: `Path to your wallpaper directory for "${(modelData.name || "Unknown")}" monitor` - text: WallpaperService.getMonitorWallpaperDirectory(modelData.name) + text: WallpaperService.getMonitorDirectory(modelData.name) labelColor: Color.mSecondary onEditingFinished: WallpaperService.setMonitorWallpaperDirectory(modelData.name, text) Layout.maximumWidth: 420 * scaling diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 4ddb792..dd44f86 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -13,6 +13,7 @@ Singleton { Logger.log("Wallpaper", "Service started") } + // All available wallpaper transitions readonly property ListModel transitionsModel: ListModel { ListElement { key: "none" @@ -29,13 +30,20 @@ Singleton { Connections { target: Settings.data.wallpaper - function onDirectoryChanged() { console.log("ondirchanged") ; root.listWallpapers() } - function onRandomEnabledChanged() { root.toggleRandomWallpaper() } - function onRandomIntervalSecChanged() { root.restartRandomWallpaperTimer() } + function onDirectoryChanged() { + root.listWallpapers() + } + function onRandomEnabledChanged() { + root.toggleRandomWallpaper() + } + function onRandomIntervalSecChanged() { + root.restartRandomWallpaperTimer() + } } // ------------------------------------------------------------------- - function geMonitorDefinition(screenName) { + // Get specific monitor wallpaper data + function getMonitorConfig(screenName) { var monitors = Settings.data.wallpaper.monitors if (monitors !== undefined) { for (var i = 0; i < monitors.length; i++) { @@ -47,42 +55,36 @@ Singleton { } // ------------------------------------------------------------------- - function getMonitorWallpaperDirectory(screenName) { - var monitor = geMonitorDefinition(screenName) - if (monitor !== undefined) { + // Get specific monitor directory + function getMonitorDirectory(screenName) { + var monitor = getMonitorConfig(screenName) + if (monitor !== undefined && monitor.directory !== undefined) { return monitor.directory } + + // Fall back to the main/single directory return Settings.data.wallpaper.directory } // ------------------------------------------------------------------- - function setMonitorWallpaperDirectory(screenName, directory) { - var monitor = geMonitorDefinition(screenName) + // Set specific monitor directory + function setMonitorDirectory(screenName, directory) { + var monitor = getMonitorConfig(screenName) if (monitor !== undefined) { monitor.directory = directory - return + } else { + Settings.data.wallpaper.monitors.push({ + "name": screenName, + "directory": directory, + "wallpaper": "" + }) } - - Settings.data.wallpaper.monitors.push({ - "name": screenName, - "directory": directory, - "wallpaper": "" - }) - } - - // ------------------------------------------------------------------- - function listWallpapers() { - Logger.log("Wallpaper", "Listing wallpapers") - scanning = true - wallpaperList = [] - // Set the folder directly to avoid model reset issues - folderModel.folder = "file://" + (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "") } // ------------------------------------------------------------------- + // Get specific monitor wallpaper function getWallpaper(screenName) { - // Logger.log("Wallpaper", "getWallpaper on", screenName) - var monitor = geMonitorDefinition(screenName) + var monitor = getMonitorConfig(screenName) if ((monitor !== undefined) && (monitor["wallpaper"] !== undefined)) { return monitor["wallpaper"] } @@ -91,22 +93,22 @@ Singleton { // ------------------------------------------------------------------- function changeWallpaper(screenName, path) { - Logger.log("Changing wallpaper") if (screenName !== undefined) { - setCurrentWallpaper(screenName, path) + setWallpaper(screenName, path) } else { + // If no screenName specified change for all screens for (var i = 0; i < Quickshell.screens.length; i++) { - setCurrentWallpaper(Quickshell.screens[i].name, path, false) + setWallpaper(Quickshell.screens[i].name, path) } } } // ------------------------------------------------------------------- - function setCurrentWallpaper(screenName, path) { + function setWallpaper(screenName, path) { if (path === "" || path === undefined) { return } - + if (screenName === undefined) { Logger.warn("Wallpaper", "setCurrentWallpaper", "no screen specified") return @@ -124,9 +126,9 @@ Singleton { wallpaperChanged = true Settings.data.wallpaper.monitors.push({ "name": screenName, - "directory": Settings.data.wallpaper.directory, + "directory": getMonitorDirectory(screenName), "wallpaper": path - }) + }) } // Restart the random wallpaper timer @@ -142,13 +144,16 @@ Singleton { // ------------------------------------------------------------------- function setRandomWallpaper() { - Logger.log("Wallpaper", "setRandomWallpaper"); + Logger.log("Wallpaper", "setRandomWallpaper") for (var i = 0; i < Quickshell.screens.length; i++) { var screenName = Quickshell.screens[i].name - // TODO one list per monitor - var randomIndex = Math.floor(Math.random() * wallpaperList.length) - var randomPath = wallpaperList[randomIndex] - setCurrentWallpaper(screenName, randomPath) + var wallpaperList = getWallpaperList(screenName) + + if (wallpaperList.length > 0) { + var randomIndex = Math.floor(Math.random() * wallpaperList.length) + var randomPath = wallpaperList[randomIndex] + setCurrentWallpaper(screenName, randomPath) + } } } @@ -171,6 +176,17 @@ Singleton { } } + // ------------------------------------------------------------------- + function listWallpapers() { + if (!Settings.isLoaded) { + return + } + + // TODO + Logger.log("Wallpaper", "Listing wallpapers for all monitors") + scanning = true + } + // ------------------------------------------------------------------- // ------------------------------------------------------------------- // ------------------------------------------------------------------- @@ -183,23 +199,23 @@ Singleton { triggeredOnStart: false } - FolderListModel { - id: folderModel - nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"] - showDirs: false - sortField: FolderListModel.Name - onStatusChanged: { - if (status === FolderListModel.Ready) { - var files = [] - for (var i = 0; i < count; i++) { - var directory = (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "") - var filepath = directory + "/" + get(i, "fileName") - files.push(filepath) - } - wallpaperList = files - scanning = false - Logger.log("Wallpaper", "List refreshed, count:", wallpaperList.length) - } - } - } + // FolderListModel { + // id: folderModel + // nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"] + // showDirs: false + // sortField: FolderListModel.Name + // onStatusChanged: { + // if (status === FolderListModel.Ready) { + // var files = [] + // for (var i = 0; i < count; i++) { + // var directory = (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "") + // var filepath = directory + "/" + get(i, "fileName") + // files.push(filepath) + // } + // wallpaperList = files + // scanning = false + // Logger.log("Wallpaper", "List refreshed, count:", wallpaperList.length) + // } + // } + // } } From 812ddf2ebb2fcb052c1e42c7c5b1746103825833 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 13:11:31 -0400 Subject: [PATCH 06/14] WallpaperSelector: syntax fix --- Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index be415ff..2664631 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -78,7 +78,7 @@ ColumnLayout { description: "Set selected wallpaper on all monitors at once." checked: Settings.data.wallpaper.setWallpaperOnAllMonitors onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked - visible: (wallpaperService.wallpaperList.length > 0) + visible: (WallpaperService.wallpaperList.length > 0) } // Wallpaper grid container @@ -204,7 +204,6 @@ ColumnLayout { ColumnLayout { id: fallbackColumn anchors.fill: parent - anchors.margins: Style.marginL * Scaling Item { Layout.fillHeight: true From 9bbdf5f6f6ffffda914a507bd9f789fc54b21bf0 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 14:09:05 -0400 Subject: [PATCH 07/14] Wallpaper: real support for differents folders per monitor \o/ --- .../Tabs/WallpaperSelectorTab.qml | 82 +++++++++---------- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 2 +- Services/WallpaperService.qml | 73 +++++++++++------ 3 files changed, 88 insertions(+), 69 deletions(-) diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index 2664631..f74e64e 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -67,32 +67,34 @@ ColumnLayout { icon: "refresh" tooltipText: "Refresh wallpaper list" onClicked: { - WallpaperService.listWallpapers() + WallpaperService.refreshWallpapersList() } Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } } + property list wallpapersList: WallpaperService.getWallpapersList(screen.name) + NToggle { label: "Assign selection to all monitors" description: "Set selected wallpaper on all monitors at once." checked: Settings.data.wallpaper.setWallpaperOnAllMonitors onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked - visible: (WallpaperService.wallpaperList.length > 0) + visible: (wallpapersList.length > 0) } // Wallpaper grid container Item { Layout.fillWidth: true Layout.preferredHeight: { - return Math.ceil(WallpaperService.wallpaperList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight + return Math.ceil(wallpapersList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight } GridView { id: wallpaperGridView anchors.fill: parent clip: true - model: WallpaperService.wallpaperList + model: wallpapersList boundsBehavior: Flickable.StopAtBounds flickableDirection: Flickable.AutoFlickDirection @@ -187,54 +189,52 @@ ColumnLayout { } } } - - } // Empty state - Rectangle { - color: Color.mSurface - radius: Style.radiusM * scaling - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - visible: WallpaperService.wallpaperList.length === 0 && !WallpaperService.scanning - Layout.fillWidth: true - Layout.preferredHeight: 130 * scaling + Rectangle { + color: Color.mSurface + radius: Style.radiusM * scaling + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: wallpapersList.length === 0 && !WallpaperService.scanning + Layout.fillWidth: true + Layout.preferredHeight: 130 * scaling - ColumnLayout { - id: fallbackColumn - anchors.fill: parent + ColumnLayout { + id: fallbackColumn + anchors.fill: parent - Item { - Layout.fillHeight: true - } + Item { + Layout.fillHeight: true + } - NIcon { - text: "folder_open" - font.pointSize: Style.fontSizeXL * scaling - color: Color.mOnSurface - Layout.alignment: Qt.AlignHCenter - } + NIcon { + text: "folder_open" + font.pointSize: Style.fontSizeXL * scaling + color: Color.mOnSurface + Layout.alignment: Qt.AlignHCenter + } - NText { - text: "No wallpaper found." - color: Color.mOnSurface - font.weight: Style.fontWeightBold - Layout.alignment: Qt.AlignHCenter - } + NText { + text: "No wallpaper found." + color: Color.mOnSurface + font.weight: Style.fontWeightBold + Layout.alignment: Qt.AlignHCenter + } - NText { - text: "Make sure your wallpaper directory is configured and contains image files." - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.alignment: Qt.AlignHCenter - } + NText { + text: "Make sure your wallpaper directory is configured and contains image files." + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter + } - Item { - Layout.fillHeight: true - } + Item { + Layout.fillHeight: true } } + } NDivider { Layout.fillWidth: true diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index f1e2b6f..18b82be 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -56,7 +56,7 @@ ColumnLayout { description: `Path to your wallpaper directory for "${(modelData.name || "Unknown")}" monitor` text: WallpaperService.getMonitorDirectory(modelData.name) labelColor: Color.mSecondary - onEditingFinished: WallpaperService.setMonitorWallpaperDirectory(modelData.name, text) + onEditingFinished: WallpaperService.setMonitorDirectory(modelData.name, text) Layout.maximumWidth: 420 * scaling } } diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index dd44f86..3fe637f 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -25,13 +25,13 @@ Singleton { } } - property var wallpaperList: [] + property var wallpaperLists: ({}) property bool scanning: false Connections { target: Settings.data.wallpaper function onDirectoryChanged() { - root.listWallpapers() + root.refreshWallpapersList() } function onRandomEnabledChanged() { root.toggleRandomWallpaper() @@ -110,15 +110,15 @@ Singleton { } if (screenName === undefined) { - Logger.warn("Wallpaper", "setCurrentWallpaper", "no screen specified") + Logger.warn("Wallpaper", "setWallpaper", "no screen specified") return } - Logger.log("Wallpaper", "setCurrentWallpaper on", screenName, ": ", path) + Logger.log("Wallpaper", "setWallpaper on", screenName, ": ", path) var wallpaperChanged = false - var monitor = geMonitorDefinition(screenName) + var monitor = getMonitorConfig(screenName) if (monitor !== undefined) { wallpaperChanged = (monitor["wallpaper"] !== path) monitor["wallpaper"] = path @@ -152,7 +152,7 @@ Singleton { if (wallpaperList.length > 0) { var randomIndex = Math.floor(Math.random() * wallpaperList.length) var randomPath = wallpaperList[randomIndex] - setCurrentWallpaper(screenName, randomPath) + setWallpaper(screenName, randomPath) } } } @@ -177,13 +177,21 @@ Singleton { } // ------------------------------------------------------------------- - function listWallpapers() { + function getWallpapersList(screenName) { + if (screenName != undefined && wallpaperLists[screenName] != undefined) { + return wallpaperLists[screenName] + } + return [] + } + + // ------------------------------------------------------------------- + function refreshWallpapersList() { if (!Settings.isLoaded) { return } // TODO - Logger.log("Wallpaper", "Listing wallpapers for all monitors") + Logger.log("Wallpaper", "refreshWallpapersList") scanning = true } @@ -199,23 +207,34 @@ Singleton { triggeredOnStart: false } - // FolderListModel { - // id: folderModel - // nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"] - // showDirs: false - // sortField: FolderListModel.Name - // onStatusChanged: { - // if (status === FolderListModel.Ready) { - // var files = [] - // for (var i = 0; i < count; i++) { - // var directory = (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "") - // var filepath = directory + "/" + get(i, "fileName") - // files.push(filepath) - // } - // wallpaperList = files - // scanning = false - // Logger.log("Wallpaper", "List refreshed, count:", wallpaperList.length) - // } - // } - // } + // Instantiator (not Repeater) to create FolderListModel for each monitor + Instantiator { + model: Quickshell.screens + + delegate: FolderListModel { + property string screenName: modelData.name + + folder: "file://" + root.getMonitorDirectory(screenName) + nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"] + showDirs: false + sortField: FolderListModel.Name + + onStatusChanged: { + if (status === FolderListModel.Ready) { + var files = [] + for (var i = 0; i < count; i++) { + var directory = root.getMonitorDirectory(screenName) + var filepath = directory + "/" + get(i, "fileName") + files.push(filepath) + } + + var lists = root.wallpaperLists + lists[screenName] = files + root.wallpaperLists = lists + + Logger.log("Wallpaper", "List refreshed for", screenName, "count:", files.length) + } + } + } + } } From 5b8d7dbff5c60617588f56eec970bb6436cc7ab1 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 14:38:27 -0400 Subject: [PATCH 08/14] Wallpaper: fixed all edge cases when toggling on/off multi directories support and invalid directory names --- .../Tabs/WallpaperSelectorTab.qml | 12 ++++- Services/WallpaperService.qml | 44 ++++++++++++++----- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index f74e64e..6731916 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -85,6 +85,7 @@ ColumnLayout { // Wallpaper grid container Item { + visible: !WallpaperService.scanning Layout.fillWidth: true Layout.preferredHeight: { return Math.ceil(wallpapersList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight @@ -197,14 +198,21 @@ ColumnLayout { radius: Style.radiusM * scaling border.color: Color.mOutline border.width: Math.max(1, Style.borderS * scaling) - visible: wallpapersList.length === 0 && !WallpaperService.scanning + visible: wallpapersList.length === 0 || WallpaperService.scanning Layout.fillWidth: true Layout.preferredHeight: 130 * scaling ColumnLayout { - id: fallbackColumn anchors.fill: parent + visible: WallpaperService.scanning + NBusyIndicator { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + } + ColumnLayout { + anchors.fill: parent + visible: wallpapersList.length === 0 && !WallpaperService.scanning Item { Layout.fillHeight: true } diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 3fe637f..1997197 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -26,7 +26,8 @@ Singleton { } property var wallpaperLists: ({}) - property bool scanning: false + property int scanningCount: 0 + readonly property bool scanning: (scanningCount > 0) Connections { target: Settings.data.wallpaper @@ -57,6 +58,10 @@ Singleton { // ------------------------------------------------------------------- // Get specific monitor directory function getMonitorDirectory(screenName) { + if (!Settings.data.wallpaper.enableMultiMonitorDirectories) { + return Settings.data.wallpaper.directory + } + var monitor = getMonitorConfig(screenName) if (monitor !== undefined && monitor.directory !== undefined) { return monitor.directory @@ -186,13 +191,18 @@ Singleton { // ------------------------------------------------------------------- function refreshWallpapersList() { - if (!Settings.isLoaded) { - return - } - - // TODO Logger.log("Wallpaper", "refreshWallpapersList") - scanning = true + scanningCount = 0 + + // Force refresh by toggling the folder property on each FolderListModel + for (var i = 0; i < wallpaperScanners.count; i++) { + var scanner = wallpaperScanners.objectAt(i) + if (scanner) { + var currentFolder = scanner.folder + scanner.folder = "" + scanner.folder = currentFolder + } + } } // ------------------------------------------------------------------- @@ -209,8 +219,8 @@ Singleton { // Instantiator (not Repeater) to create FolderListModel for each monitor Instantiator { + id: wallpaperScanners model: Quickshell.screens - delegate: FolderListModel { property string screenName: modelData.name @@ -218,9 +228,22 @@ Singleton { nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"] showDirs: false sortField: FolderListModel.Name - onStatusChanged: { - if (status === FolderListModel.Ready) { + if (status === FolderListModel.Null) { + // Flush the list + var lists = root.wallpaperLists + lists[screenName] = [] + root.wallpaperLists = lists + } + else if (status === FolderListModel.Loading) { + // Flush the list + var lists = root.wallpaperLists + lists[screenName] = [] + root.wallpaperLists = lists + + scanningCount++ + + } else if (status === FolderListModel.Ready) { var files = [] for (var i = 0; i < count; i++) { var directory = root.getMonitorDirectory(screenName) @@ -232,6 +255,7 @@ Singleton { lists[screenName] = files root.wallpaperLists = lists + scanningCount-- Logger.log("Wallpaper", "List refreshed for", screenName, "count:", files.length) } } From f67999945327db3d6eebaef92c11adb3f1563bbf Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 14:44:20 -0400 Subject: [PATCH 09/14] Wallpaper: fixed random wallpaper --- Services/WallpaperService.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 1997197..0d4d371 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -152,7 +152,7 @@ Singleton { Logger.log("Wallpaper", "setRandomWallpaper") for (var i = 0; i < Quickshell.screens.length; i++) { var screenName = Quickshell.screens[i].name - var wallpaperList = getWallpaperList(screenName) + var wallpaperList = getWallpapersList(screenName) if (wallpaperList.length > 0) { var randomIndex = Math.floor(Math.random() * wallpaperList.length) @@ -234,15 +234,13 @@ Singleton { var lists = root.wallpaperLists lists[screenName] = [] root.wallpaperLists = lists - } - else if (status === FolderListModel.Loading) { + } else if (status === FolderListModel.Loading) { // Flush the list var lists = root.wallpaperLists lists[screenName] = [] root.wallpaperLists = lists scanningCount++ - } else if (status === FolderListModel.Ready) { var files = [] for (var i = 0; i < count; i++) { From a2ecc676435b60d9f701cb00c64508bcacf330bc Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 14:57:03 -0400 Subject: [PATCH 10/14] Wallpaper: less intrusive UI when using per monitor directories --- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 45 +++++++++++---------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index 18b82be..9143f7c 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -15,7 +15,7 @@ ColumnLayout { Layout.fillWidth: true NTextInput { label: "Wallpaper Directory" - description: "Path to your wallpaper directory." + description: "Path to your common wallpaper directory." text: Settings.data.wallpaper.directory onEditingFinished: { Settings.data.wallpaper.directory = text @@ -31,31 +31,34 @@ ColumnLayout { onToggled: checked => Settings.data.wallpaper.enableMultiMonitorDirectories = checked } - ColumnLayout { + NBox { visible: Settings.data.wallpaper.enableMultiMonitorDirectories - spacing: Style.marginL * scaling - Repeater { - model: Quickshell.screens || [] - delegate: Rectangle { - Layout.fillWidth: true - Layout.minimumWidth: 550 * scaling - radius: Style.radiusM * scaling - color: Color.mSurface - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling - ColumnLayout { - id: contentCol - anchors.fill: parent - anchors.margins: Style.marginL * scaling - spacing: Style.marginXXS * scaling + Layout.fillWidth: true + Layout.minimumWidth: 550 * scaling + radius: Style.radiusM * scaling + color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling + ColumnLayout { + id: contentCol + anchors.fill: parent + anchors.margins: Style.marginXL * scaling + spacing: Style.marginM * scaling + Repeater { + model: Quickshell.screens || [] + delegate: RowLayout { + NText { + text: (modelData.name || "Unknown") + color: Color.mSecondary + font.weight: Style.fontWeightBold + Layout.preferredWidth: 90 * scaling + } NTextInput { - label: (modelData.name || "Unknown") - description: `Path to your wallpaper directory for "${(modelData.name || "Unknown")}" monitor` + Layout.fillWidth: true text: WallpaperService.getMonitorDirectory(modelData.name) - labelColor: Color.mSecondary onEditingFinished: WallpaperService.setMonitorDirectory(modelData.name, text) Layout.maximumWidth: 420 * scaling } From 61d13a6cab6c8ecc0427e66c6370f5c77f82d684 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 15:21:10 -0400 Subject: [PATCH 11/14] Wallpaper: minor fixes for random wallpaper picking --- Services/WallpaperService.qml | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 0d4d371..daafb97 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -150,14 +150,28 @@ Singleton { // ------------------------------------------------------------------- function setRandomWallpaper() { Logger.log("Wallpaper", "setRandomWallpaper") - for (var i = 0; i < Quickshell.screens.length; i++) { - var screenName = Quickshell.screens[i].name - var wallpaperList = getWallpapersList(screenName) + if (Settings.data.wallpaper.enableMultiMonitorDirectories) { + // Pick a random wallpaper per screen + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name + var wallpaperList = getWallpapersList(screenName) + + if (wallpaperList.length > 0) { + var randomIndex = Math.floor(Math.random() * wallpaperList.length) + var randomPath = wallpaperList[randomIndex] + changeWallpaper(screenName, randomPath) + } + } + } + else { + // Pick a random wallpaper for all screens + // We can use any screenName here, so we just pick the primary one. + var wallpaperList = getWallpapersList(Screen.name) if (wallpaperList.length > 0) { var randomIndex = Math.floor(Math.random() * wallpaperList.length) var randomPath = wallpaperList[randomIndex] - setWallpaper(screenName, randomPath) + changeWallpaper(undefined, randomPath) } } } @@ -165,11 +179,9 @@ Singleton { // ------------------------------------------------------------------- function toggleRandomWallpaper() { Logger.log("Wallpaper", "toggleRandomWallpaper") - if (Settings.data.wallpaper.randomEnabled && !randomWallpaperTimer.running) { - randomWallpaperTimer.start() + if (Settings.data.wallpaper.randomEnabled) { + randomWallpaperTimer.restart() setRandomWallpaper() - } else if (!Settings.data.wallpaper.randomEnabled && randomWallpaperTimer.running) { - randomWallpaperTimer.stop() } } @@ -211,7 +223,7 @@ Singleton { Timer { id: randomWallpaperTimer interval: Settings.data.wallpaper.randomIntervalSec * 1000 - running: false + running: Settings.data.wallpaper.randomEnabled repeat: true onTriggered: setRandomWallpaper() triggeredOnStart: false From 63e90a5c175c43281a605b59e7ce6da0e528899e Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 16:26:48 -0400 Subject: [PATCH 12/14] Wallpaper: cool fade in transition via shader --- Modules/Background/Background.qml | 84 +++++++++++++++++++++++++++++- Services/WallpaperService.qml | 5 +- Shaders/frag/mix_images.frag | 22 ++++++++ Shaders/qsb/mix_images.frag.qsb | Bin 0 -> 1548 bytes 4 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 Shaders/frag/mix_images.frag create mode 100644 Shaders/qsb/mix_images.frag.qsb diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index ef6cd39..b896099 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -5,6 +5,7 @@ import qs.Commons import qs.Services Variants { + id: backgroundVariants model: Quickshell.screens delegate: Loader { @@ -14,6 +15,25 @@ Variants { active: Settings.isLoaded && WallpaperService.getWallpaper(modelData.name) sourceComponent: PanelWindow { + id: root + + readonly property real transitionDuration: Settings.data.wallpaper.transitionDuration + readonly property real transitionType: Settings.data.wallpaper.transitionType + + property string servicedWallpaper: WallpaperService.getWallpaper(modelData.name) + onServicedWallpaperChanged: { + if (servicedWallpaper && servicedWallpaper !== currentWallpaper.source) { + if (Settings.data.wallpaper.transitionType == 'fade') { + setWallpaperWithTransition(servicedWallpaper) + } else { + setWallpaperImmediate(servicedWallpaper) + } + } + } + + // Internal state management + property bool transitioning: false + property real fadeValue: 0.0 color: Color.transparent screen: modelData @@ -29,12 +49,74 @@ Variants { } Image { + id: currentWallpaper anchors.fill: parent fillMode: Image.PreserveAspectCrop - source: WallpaperService.getWallpaper(modelData.name) + source: "" cache: true smooth: true mipmap: false + visible: false + } + + Image { + id: nextWallpaper + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + source: "" + cache: true + smooth: true + mipmap: false + visible: false + } + + ShaderEffect { + id: shaderEffect + anchors.fill: parent + + property variant source1: currentWallpaper + property variant source2: nextWallpaper + property real fade: fadeValue + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/mix_images.frag.qsb") + } + + // Animation for the fade value + NumberAnimation { + id: fadeAnimation + target: root + property: "fadeValue" + from: 0.0 + to: 1.0 + duration: Settings.data.wallpaper.transitionDuration + easing.type: Easing.InOutQuad + + onFinished: { + // Swap images after transition completes + currentWallpaper.source = nextWallpaper.source + fadeValue = 0.0 + transitioning = false + } + } + + function startTransition() { + if (!transitioning && nextWallpaper.source != currentWallpaper.source) { + transitioning = true + fadeAnimation.start() + } + } + + function setWallpaperImmediate(source) { + currentWallpaper.source = source + nextWallpaper.source = source + fadeValue = 0.0 + transitioning = false + } + + function setWallpaperWithTransition(source) { + if (source != currentWallpaper.source) { + nextWallpaper.source = source + startTransition() + } } } } diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index daafb97..ea3a594 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -163,9 +163,8 @@ Singleton { changeWallpaper(screenName, randomPath) } } - } - else { - // Pick a random wallpaper for all screens + } else { + // Pick a random wallpaper common to all screens // We can use any screenName here, so we just pick the primary one. var wallpaperList = getWallpapersList(Screen.name) if (wallpaperList.length > 0) { diff --git a/Shaders/frag/mix_images.frag b/Shaders/frag/mix_images.frag new file mode 100644 index 0000000..10c3a63 --- /dev/null +++ b/Shaders/frag/mix_images.frag @@ -0,0 +1,22 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(binding = 1) uniform sampler2D source1; +layout(binding = 2) uniform sampler2D source2; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float fade; +}; + +void main() { + vec4 color1 = texture(source1, qt_TexCoord0); + vec4 color2 = texture(source2, qt_TexCoord0); + + // Smooth cross-fade using smoothstep for better visual quality + float smoothFade = smoothstep(0.0, 1.0, fade); + + // Mix the two textures based on fade value + fragColor = mix(color1, color2, smoothFade) * qt_Opacity; +} \ No newline at end of file diff --git a/Shaders/qsb/mix_images.frag.qsb b/Shaders/qsb/mix_images.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..e06417c6f07afa3020f1dea72d0ef13311f0fd73 GIT binary patch literal 1548 zcmV+n2J`s<02HBkob6cKZrer>9ZPcTFlp}hq{$|3Q#w%@iE)a+iroZu(*#K1AU4th zfzK$9~(b9VO3&XN`Y z90q_2089YD1$YetJXiw`eOQ775=fy1H2^390Q^A*j5(0t6oin14{aITk>QH|z|cdm z0Xc;7&V#$)0>BdkQLjQh7Cr<1i(4z_aT|v5E(HdN&GGCkM!6={9{ z`30!JhlI!ZT_sJ?hd8A}1+Gy(S82>^q!0SeLPhG$D5u+G-!SGdL-(5R6WSA|h!1U> zd^+ACzRzG%#_AqM%x_SV>!(5WIBamCea5g+k4t1PKt<|)j5J3Zbv;Fzeg%^fcbYKh zUxc#cKW)UHBh7XE`9k~!!knl0Gvs>&lTwS1NUs~jfx3K5{-ZE0?VB|JI@RwAVUX_< zJ;zuaZgW} zzBc)OhY7f^I-^2+&;;#s)Giah&TR2Ci;|etYc;Ev@*tAdX4Ky7v9Oc)qOr>z#iC(Z znMm_Mu#G&l-dKz=*7e04do8@@zDT2fL;55GI#+sr5Q%Nm8~QEYuyz`jmB&$-q+R4< z-d)%5r~xy-+w1VuU1nL5rva}w4ges2~rH%Y|iW531O0vVa$J-g=A zs;rKGyt}T00#R7I<2_FO_OdVhW^J71!-r4sK`-2cn^{$>5qDC^I|vflfTmNkmUHENOz)n#Gox0i64zkTz*d+Yua zjXOyEZ|hlnJL@k}be{`0@-XD7tie1>c{|DkPwkDGmM2ZV^Sb^wNvb9>*Pp{7yg|X) zJnQ+vw@DagT-d?rj0s7}6rgjvpg8p+$l0D%t7T1Zkq=ZNi+XLCc?%qWRV|TK;S0_@ zLV2`sOmv3my?EUb-ufQA?pR(#Tbb}h6sYMH*QWVwbb(TTt-g5r@|CxgCRye^jeEJE za*SbP^K9*lua{O#> zE{eT(GZ+m-LKZj%!oFWag|ef<>R?@sX{ioRd%JpS$m#U^{XVsnS7@o-(yC2mSJ_x5 z{5JO@yw@U*6mP*qIHRjXCrmX^jxj?khC~_)WO^KP&c;%c-OWXw#sz{MbH~7MdHsJ7 ze$wBciQ=G>w>W#y<-+fHnP_#QjYVtd&!TSY4*qdA7cDE#@Ixy0yFBap0cSF5+$Z2* ze|fK$3DIHJHJr&_OTF2Ym9}fM>+548qj;Z2Yc}(Yr*$uk9O!%>!{7qb5tl3oiuuQ8u_#AAhJ!!brHJkc%voFv!fOQGfyA%2q|hwm-nlBf ygWkGEZzZawu%{-aYV;Z|yo%}PANmEa@FN9c(Z;jTkZ&EQf9NR2%i~XD`kmn)s~n5~ literal 0 HcmV?d00001 From 2f9eb28596956d61b4f36ed588e139949d363d9e Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 16:53:43 -0400 Subject: [PATCH 13/14] Wallpaper: On startup set wallpaper without transition --- Modules/Background/Background.qml | 33 +++++++++++++++---- .../Tabs/WallpaperSelectorTab.qml | 2 +- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 2 +- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index b896099..1911f34 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -17,13 +17,24 @@ Variants { sourceComponent: PanelWindow { id: root - readonly property real transitionDuration: Settings.data.wallpaper.transitionDuration - readonly property real transitionType: Settings.data.wallpaper.transitionType + // Internal state management + property bool transitioning: false + property real fadeValue: 0.0 + property bool firstWallpaper: true + // External state management property string servicedWallpaper: WallpaperService.getWallpaper(modelData.name) onServicedWallpaperChanged: { if (servicedWallpaper && servicedWallpaper !== currentWallpaper.source) { - if (Settings.data.wallpaper.transitionType == 'fade') { + + // Set wallpaper immediately on startup + if (firstWallpaper) { + firstWallpaper = false + setWallpaperImmediate(servicedWallpaper) + return + } + + if (Settings.data.wallpaper.transitionType === 'fade') { setWallpaperWithTransition(servicedWallpaper) } else { setWallpaperImmediate(servicedWallpaper) @@ -31,10 +42,6 @@ Variants { } } - // Internal state management - property bool transitioning: false - property real fadeValue: 0.0 - color: Color.transparent screen: modelData WlrLayershell.layer: WlrLayer.Background @@ -114,6 +121,18 @@ Variants { function setWallpaperWithTransition(source) { if (source != currentWallpaper.source) { + + if (transitioning) { + // we are interupting a transition + if (fadeValue >= 0.5) { + + } + currentWallpaper.source = nextWallpaper.source + fadeAnimation.stop() + fadeValue = 0 + transitioning = false + } + nextWallpaper.source = source startTransition() } diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index 6731916..b708f9d 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -98,7 +98,7 @@ ColumnLayout { model: wallpapersList boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.AutoFlickDirection + flickableDirection: Flickable.VerticalFlick interactive: false property int columns: 5 diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index 9143f7c..546a4ca 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -114,7 +114,7 @@ ColumnLayout { NSlider { Layout.fillWidth: true from: 100 - to: 10000 + to: 5000 stepSize: 100 value: Settings.data.wallpaper.transitionDuration onMoved: Settings.data.wallpaper.transitionDuration = value From c4e4f78336c2d11ced8b9c5b87c865d9045088a0 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 17:00:58 -0400 Subject: [PATCH 14/14] Wallpaper/Matugen: Matugen always based on the primary screen wallpaper --- Services/MatugenService.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml index 939324b..9c40aaf 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -22,10 +22,11 @@ Singleton { // Ensure cache dir exists Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]) - // TODO: fix matugen + Logger.log("Matugen", "Generating from wallpaper on screen:", Screen.name) + var wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''") + var content = buildConfigToml() var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - var wp = WallpaperService.currentWallpaper.replace(/'/g, "'\\''") var pathEsc = dynamicConfigPath.replace(/'/g, "'\\''") var extraRepo = (Quickshell.shellDir + "/Assets/Matugen/extra").replace(/'/g, "'\\''") var extraUser = (Settings.configDir + "matugen.d").replace(/'/g, "'\\''")