From e79c163dd91960fa053c02a7d1880c27b8ca7e9f Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 08:33:40 -0400 Subject: [PATCH] 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) } }