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", "--------------------------") + } } diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 17187be..b2759fb 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -89,23 +89,16 @@ Singleton { reload() } 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 - 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 (!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) @@ -171,22 +164,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 +218,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..1911f34 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -4,27 +4,44 @@ import Quickshell.Wayland import qs.Commons import qs.Services -Loader { - active: !Settings.data.wallpaper.swww.enabled +Variants { + id: backgroundVariants + model: Quickshell.screens - sourceComponent: Variants { - model: Quickshell.screens + delegate: Loader { - delegate: PanelWindow { - required property ShellScreen modelData - property string wallpaperSource: WallpaperService.currentWallpaper !== "" - && !Settings.data.wallpaper.swww.enabled ? WallpaperService.currentWallpaper : "" + required property ShellScreen modelData - visible: wallpaperSource !== "" && !Settings.data.wallpaper.swww.enabled + active: Settings.isLoaded && WallpaperService.getWallpaper(modelData.name) - // Force update when SWWW setting changes - onVisibleChanged: { - if (visible) { + sourceComponent: PanelWindow { + id: root - } else { + // 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) { + + // Set wallpaper immediately on startup + if (firstWallpaper) { + firstWallpaper = false + setWallpaperImmediate(servicedWallpaper) + return + } + + if (Settings.data.wallpaper.transitionType === 'fade') { + setWallpaperWithTransition(servicedWallpaper) + } else { + setWallpaperImmediate(servicedWallpaper) + } } } + color: Color.transparent screen: modelData WlrLayershell.layer: WlrLayer.Background @@ -38,18 +55,87 @@ Loader { left: true } - margins { - top: 0 - } - Image { + id: currentWallpaper anchors.fill: parent fillMode: Image.PreserveAspectCrop - source: wallpaperSource - visible: wallpaperSource !== "" + 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) { + + 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/Background/Overview.qml b/Modules/Background/Overview.qml index e673663..32237d1 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -6,24 +6,19 @@ 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 - sourceComponent: Variants { - model: Quickshell.screens + active: Settings.isLoaded && CompositorService.isNiri - delegate: PanelWindow { - required property ShellScreen modelData - property string wallpaperSource: WallpaperService.currentWallpaper !== "" - && !Settings.data.wallpaper.swww.enabled ? WallpaperService.currentWallpaper : "" + sourceComponent: PanelWindow { + Component.onCompleted: { + Logger.log("Overview", "Loading Overview component for Niri on", modelData.name) + } - visible: wallpaperSource !== "" && !Settings.data.wallpaper.swww.enabled color: Color.transparent screen: modelData WlrLayershell.layer: WlrLayer.Background @@ -39,19 +34,15 @@ Loader { Image { id: bgImage - anchors.fill: parent fillMode: Image.PreserveAspectCrop - source: wallpaperSource + source: WallpaperService.getWallpaper(modelData.name) 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/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..b708f9d 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -23,13 +23,12 @@ 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.currentWallpaper + imagePath: WallpaperService.getWallpaper(screen.name) fallbackIcon: "image" imageRadius: Style.radiusM * scaling } @@ -62,41 +61,44 @@ 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 { icon: "refresh" tooltipText: "Refresh wallpaper list" onClicked: { - WallpaperService.listWallpapers() + WallpaperService.refreshWallpapersList() } - Layout.alignment: Qt.AlignTop | Qt.AlignRight + 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: (wallpapersList.length > 0) + } + // Wallpaper grid container Item { + visible: !WallpaperService.scanning 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 + flickableDirection: Flickable.VerticalFlick interactive: false property int columns: 5 @@ -114,7 +116,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) @@ -179,46 +181,65 @@ 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) + } } } } } + } - // Empty state - Rectangle { + // Empty state + 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 { 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 + visible: WallpaperService.scanning + NBusyIndicator { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + } - ColumnLayout { - anchors.centerIn: parent - spacing: Style.marginM * scaling + ColumnLayout { + anchors.fill: parent + visible: wallpapersList.length === 0 && !WallpaperService.scanning + Item { + Layout.fillHeight: true + } - NIcon { - text: "folder_open" - font.pointSize: Style.fontSizeL * 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 wallpapers 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.mOnSurface - wrapMode: Text.WordWrap - horizontalAlignment: Text.AlignHCenter - Layout.preferredWidth: Style.sliderWidth * 1.5 * scaling - } + 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 } } } diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index e96b5ef..546a4ca 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,62 @@ 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 common 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 + } + + NBox { + visible: Settings.data.wallpaper.enableMultiMonitorDirectories + + 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 { + Layout.fillWidth: true + text: WallpaperService.getMonitorDirectory(modelData.name) + onEditingFinished: WallpaperService.setMonitorDirectory(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 +89,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: 5000 + 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 +138,28 @@ 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 +167,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 +201,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 +215,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 +226,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 } } 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/MatugenService.qml b/Services/MatugenService.qml index 1749410..9c40aaf 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -22,9 +22,11 @@ Singleton { // Ensure cache dir exists Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]) + 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, "'\\''") 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..ea3a594 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -10,84 +10,181 @@ Singleton { id: root Component.onCompleted: { - Logger.log("Wallpapers", "Service started") - listWallpapers() - - // Wallpaper is set when the settings are loaded. - // Don't start random wallpaper during initialization + Logger.log("Wallpaper", "Service started") } - 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"] - - function listWallpapers() { - Logger.log("Wallpapers", "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 setCurrentWallpaper(path, isInitial) { - // Only regenerate colors if the wallpaper actually changed - var wallpaperChanged = currentWallpaper !== path - - currentWallpaper = path - if (!isInitial) { - Settings.data.wallpaper.current = path + // All available wallpaper transitions + readonly property ListModel transitionsModel: ListModel { + ListElement { + key: "none" + name: "None" } - 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 + ListElement { + key: "fade" + name: "Fade" + } + } + + property var wallpaperLists: ({}) + property int scanningCount: 0 + readonly property bool scanning: (scanningCount > 0) + + Connections { + target: Settings.data.wallpaper + function onDirectoryChanged() { + root.refreshWallpapersList() + } + function onRandomEnabledChanged() { + root.toggleRandomWallpaper() + } + function onRandomIntervalSecChanged() { + root.restartRandomWallpaperTimer() + } + } + + // ------------------------------------------------------------------- + // 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++) { + if (monitors[i].name !== undefined && monitors[i].name === screenName) { + return monitors[i] + } } + } + } - changeWallpaperProcess.running = true - } else { - - // Fallback: update the settings directly for non-SWWW mode - //Logger.log("Wallpapers", "Not using Swww, setting wallpaper directly") + // ------------------------------------------------------------------- + // 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 + } + + // Fall back to the main/single directory + return Settings.data.wallpaper.directory + } + + // ------------------------------------------------------------------- + // Set specific monitor directory + function setMonitorDirectory(screenName, directory) { + var monitor = getMonitorConfig(screenName) + if (monitor !== undefined) { + monitor.directory = directory + } else { + Settings.data.wallpaper.monitors.push({ + "name": screenName, + "directory": directory, + "wallpaper": "" + }) + } + } + + // ------------------------------------------------------------------- + // Get specific monitor wallpaper + function getWallpaper(screenName) { + var monitor = getMonitorConfig(screenName) + if ((monitor !== undefined) && (monitor["wallpaper"] !== undefined)) { + return monitor["wallpaper"] + } + return "" + } + + // ------------------------------------------------------------------- + function changeWallpaper(screenName, path) { + if (screenName !== undefined) { + setWallpaper(screenName, path) + } else { + // If no screenName specified change for all screens + for (var i = 0; i < Quickshell.screens.length; i++) { + setWallpaper(Quickshell.screens[i].name, path) + } + } + } + + // ------------------------------------------------------------------- + function setWallpaper(screenName, path) { + if (path === "" || path === undefined) { + return + } + + if (screenName === undefined) { + Logger.warn("Wallpaper", "setWallpaper", "no screen specified") + return + } + + Logger.log("Wallpaper", "setWallpaper on", screenName, ": ", path) + + var wallpaperChanged = false + + var monitor = getMonitorConfig(screenName) + if (monitor !== undefined) { + wallpaperChanged = (monitor["wallpaper"] !== path) + monitor["wallpaper"] = path + } else { + wallpaperChanged = true + Settings.data.wallpaper.monitors.push({ + "name": screenName, + "directory": getMonitorDirectory(screenName), + "wallpaper": path + }) + } + + // Restart the random wallpaper timer if (randomWallpaperTimer.running) { randomWallpaperTimer.restart() } - // Only notify ColorScheme service if the wallpaper actually changed + // 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") + + 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 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) { + var randomIndex = Math.floor(Math.random() * wallpaperList.length) + var randomPath = wallpaperList[randomIndex] + changeWallpaper(undefined, randomPath) + } } - setCurrentWallpaper(randomPath, false) } + // ------------------------------------------------------------------- function toggleRandomWallpaper() { - if (Settings.data.wallpaper.isRandom && !randomWallpaperTimer.running) { - randomWallpaperTimer.start() + Logger.log("Wallpaper", "toggleRandomWallpaper") + if (Settings.data.wallpaper.randomEnabled) { + randomWallpaperTimer.restart() setRandomWallpaper() - } else if (!Settings.data.randomWallpaper && randomWallpaperTimer.running) { - randomWallpaperTimer.stop() } } + // ------------------------------------------------------------------- function restartRandomWallpaperTimer() { if (Settings.data.wallpaper.isRandom) { randomWallpaperTimer.stop() @@ -95,78 +192,81 @@ Singleton { } } - function startSWWWDaemon() { - if (Settings.data.wallpaper.swww.enabled) { - Logger.log("Swww", "Requesting swww-daemon") - startDaemonProcess.running = true + // ------------------------------------------------------------------- + function getWallpapersList(screenName) { + if (screenName != undefined && wallpaperLists[screenName] != undefined) { + return wallpaperLists[screenName] + } + return [] + } + + // ------------------------------------------------------------------- + function refreshWallpapersList() { + Logger.log("Wallpaper", "refreshWallpapersList") + 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 + } } } + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- Timer { id: randomWallpaperTimer - interval: Settings.data.wallpaper.randomInterval * 1000 - running: false + interval: Settings.data.wallpaper.randomIntervalSec * 1000 + running: Settings.data.wallpaper.randomEnabled repeat: true onTriggered: setRandomWallpaper() triggeredOnStart: false } - 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 - 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) + // Instantiator (not Repeater) to create FolderListModel for each monitor + Instantiator { + id: wallpaperScanners + 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.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) + var filepath = directory + "/" + get(i, "fileName") + files.push(filepath) + } + + var lists = root.wallpaperLists + lists[screenName] = files + root.wallpaperLists = lists + + scanningCount-- + Logger.log("Wallpaper", "List refreshed for", screenName, "count:", files.length) } - 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") } } } 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 0000000..e06417c Binary files /dev/null and b/Shaders/qsb/mix_images.frag.qsb differ 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) } } 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 ];