1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-25 14:02:53 -05:00

add wallpaper cycling feature

This commit is contained in:
bbedward
2025-08-09 23:50:45 -04:00
parent 01b5ae46b1
commit 42abed62fe
4 changed files with 522 additions and 4 deletions

View File

@@ -6,6 +6,7 @@ import QtCore
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
Singleton {
@@ -21,6 +22,10 @@ Singleton {
property bool nvidiaGpuTempEnabled: false
property bool nonNvidiaGpuTempEnabled: false
property var enabledGpuPciIds: []
property bool wallpaperCyclingEnabled: false
property string wallpaperCyclingMode: "interval" // "interval" or "time"
property int wallpaperCyclingInterval: 300 // seconds (5 minutes)
property string wallpaperCyclingTime: "06:00" // HH:mm format
Component.onCompleted: {
loadSettings()
@@ -45,6 +50,10 @@ Singleton {
nvidiaGpuTempEnabled = settings.nvidiaGpuTempEnabled !== undefined ? settings.nvidiaGpuTempEnabled : false
nonNvidiaGpuTempEnabled = settings.nonNvidiaGpuTempEnabled !== undefined ? settings.nonNvidiaGpuTempEnabled : false
enabledGpuPciIds = settings.enabledGpuPciIds !== undefined ? settings.enabledGpuPciIds : []
wallpaperCyclingEnabled = settings.wallpaperCyclingEnabled !== undefined ? settings.wallpaperCyclingEnabled : false
wallpaperCyclingMode = settings.wallpaperCyclingMode !== undefined ? settings.wallpaperCyclingMode : "interval"
wallpaperCyclingInterval = settings.wallpaperCyclingInterval !== undefined ? settings.wallpaperCyclingInterval : 300
wallpaperCyclingTime = settings.wallpaperCyclingTime !== undefined ? settings.wallpaperCyclingTime : "06:00"
}
} catch (e) {
@@ -62,7 +71,11 @@ Singleton {
"selectedGpuIndex": selectedGpuIndex,
"nvidiaGpuTempEnabled": nvidiaGpuTempEnabled,
"nonNvidiaGpuTempEnabled": nonNvidiaGpuTempEnabled,
"enabledGpuPciIds": enabledGpuPciIds
"enabledGpuPciIds": enabledGpuPciIds,
"wallpaperCyclingEnabled": wallpaperCyclingEnabled,
"wallpaperCyclingMode": wallpaperCyclingMode,
"wallpaperCyclingInterval": wallpaperCyclingInterval,
"wallpaperCyclingTime": wallpaperCyclingTime
}, null, 2))
}
@@ -147,6 +160,26 @@ Singleton {
saveSettings()
}
function setWallpaperCyclingEnabled(enabled) {
wallpaperCyclingEnabled = enabled
saveSettings()
}
function setWallpaperCyclingMode(mode) {
wallpaperCyclingMode = mode
saveSettings()
}
function setWallpaperCyclingInterval(interval) {
wallpaperCyclingInterval = interval
saveSettings()
}
function setWallpaperCyclingTime(time) {
wallpaperCyclingTime = time
saveSettings()
}
FileView {
id: settingsFile
@@ -189,6 +222,32 @@ Singleton {
root.setWallpaper("")
return "SUCCESS: Wallpaper cleared"
}
function next(): string {
if (!root.wallpaperPath) {
return "ERROR: No wallpaper set"
}
try {
WallpaperCyclingService.cycleNextManually()
return "SUCCESS: Cycling to next wallpaper"
} catch (e) {
return "ERROR: Failed to cycle wallpaper: " + e.toString()
}
}
function prev(): string {
if (!root.wallpaperPath) {
return "ERROR: No wallpaper set"
}
try {
WallpaperCyclingService.cyclePrevManually()
return "SUCCESS: Cycling to previous wallpaper"
} catch (e) {
return "ERROR: Failed to cycle wallpaper: " + e.toString()
}
}
}
IpcHandler {

View File

@@ -13,6 +13,11 @@ Item {
property alias profileBrowser: profileBrowserLoader.item
property alias wallpaperBrowser: wallpaperBrowserLoader.item
Component.onCompleted: {
// Access WallpaperCyclingService to ensure it's initialized
WallpaperCyclingService.cyclingActive
}
DankFlickable {
anchors.fill: parent
anchors.topMargin: Theme.spacingL
@@ -487,6 +492,264 @@ Item {
}
}
}
// Wallpaper Cycling Section
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.2
visible: SessionData.wallpaperPath !== ""
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: SessionData.wallpaperPath !== ""
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "schedule"
size: Theme.iconSize
color: SessionData.wallpaperCyclingEnabled ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - controlsRow.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: "Wallpaper Cycling"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: "Automatically cycle through wallpapers in the same folder"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
Row {
id: controlsRow
spacing: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
StyledRect {
width: 60
height: 32
radius: Theme.cornerRadius
color: prevButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8) : Theme.primary
opacity: SessionData.wallpaperPath ? 1 : 0.5
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "skip_previous"
size: Theme.iconSizeSmall
color: Theme.primaryText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Prev"
color: Theme.primaryText
font.pixelSize: Theme.fontSizeSmall
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: prevButtonArea
anchors.fill: parent
enabled: SessionData.wallpaperPath
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
hoverEnabled: true
onClicked: {
WallpaperCyclingService.cyclePrevManually()
}
}
}
StyledRect {
width: 60
height: 32
radius: Theme.cornerRadius
color: nextButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8) : Theme.primary
opacity: SessionData.wallpaperPath ? 1 : 0.5
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "skip_next"
size: Theme.iconSizeSmall
color: Theme.primaryText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Next"
color: Theme.primaryText
font.pixelSize: Theme.fontSizeSmall
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: nextButtonArea
anchors.fill: parent
enabled: SessionData.wallpaperPath
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
hoverEnabled: true
onClicked: {
WallpaperCyclingService.cycleNextManually()
}
}
}
DankToggle {
id: cyclingToggle
anchors.verticalCenter: parent.verticalCenter
checked: SessionData.wallpaperCyclingEnabled
onToggled: toggled => SessionData.setWallpaperCyclingEnabled(toggled)
}
}
}
// Cycling mode and settings
Column {
width: parent.width
spacing: Theme.spacingS
visible: SessionData.wallpaperCyclingEnabled
leftPadding: Theme.iconSize + Theme.spacingM
Row {
spacing: Theme.spacingL
width: parent.width - parent.leftPadding
StyledText {
text: "Mode:"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankTabBar {
id: modeTabBar
width: 200
height: 32
model: [
{ text: "Interval" },
{ text: "Time" }
]
currentIndex: SessionData.wallpaperCyclingMode === "time" ? 1 : 0
onTabClicked: (index) => {
SessionData.setWallpaperCyclingMode(index === 1 ? "time" : "interval")
}
}
}
// Interval settings
DankDropdown {
width: parent.width - parent.leftPadding
visible: SessionData.wallpaperCyclingMode === "interval"
text: "Interval"
description: "How often to change wallpaper"
property var intervalOptions: [
"1 minute", "5 minutes", "15 minutes", "30 minutes",
"1 hour", "1.5 hours", "2 hours", "3 hours",
"4 hours", "6 hours", "8 hours", "12 hours"
]
property var intervalValues: [
60, 300, 900, 1800,
3600, 5400, 7200, 10800,
14400, 21600, 28800, 43200
]
options: intervalOptions
currentValue: {
const currentSeconds = SessionData.wallpaperCyclingInterval
const index = intervalValues.indexOf(currentSeconds)
return index >= 0 ? intervalOptions[index] : "5 minutes"
}
onValueChanged: (value) => {
const index = intervalOptions.indexOf(value)
if (index >= 0) {
SessionData.setWallpaperCyclingInterval(intervalValues[index])
}
}
}
// Time settings
Row {
spacing: Theme.spacingM
visible: SessionData.wallpaperCyclingMode === "time"
width: parent.width - parent.leftPadding
StyledText {
text: "Daily at:"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
width: 100
height: 40
text: SessionData.wallpaperCyclingTime
placeholderText: "00:00"
maximumLength: 5
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
validator: RegularExpressionValidator {
regularExpression: /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/
}
onAccepted: {
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text)
if (isValid) {
SessionData.setWallpaperCyclingTime(text)
} else {
// Reset to current value if invalid
text = SessionData.wallpaperCyclingTime
}
}
onEditingFinished: {
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text)
if (isValid) {
SessionData.setWallpaperCyclingTime(text)
} else {
// Reset to current value if invalid
text = SessionData.wallpaperCyclingTime
}
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "24-hour format"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
}
}

View File

@@ -0,0 +1,195 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
Singleton {
id: root
property bool cyclingActive: false
property string cachedCyclingTime: SessionData.wallpaperCyclingTime
property int cachedCyclingInterval: SessionData.wallpaperCyclingInterval
property string lastTimeCheck: ""
Component.onCompleted: {
updateCyclingState()
}
Connections {
target: SessionData
function onWallpaperCyclingEnabledChanged() {
updateCyclingState()
}
function onWallpaperCyclingModeChanged() {
updateCyclingState()
}
function onWallpaperCyclingIntervalChanged() {
cachedCyclingInterval = SessionData.wallpaperCyclingInterval
if (SessionData.wallpaperCyclingMode === "interval") {
updateCyclingState()
}
}
function onWallpaperCyclingTimeChanged() {
cachedCyclingTime = SessionData.wallpaperCyclingTime
if (SessionData.wallpaperCyclingMode === "time") {
updateCyclingState()
}
}
}
function updateCyclingState() {
if (SessionData.wallpaperCyclingEnabled && SessionData.wallpaperPath) {
startCycling()
} else {
stopCycling()
}
}
function startCycling() {
if (SessionData.wallpaperCyclingMode === "interval") {
intervalTimer.interval = cachedCyclingInterval * 1000
intervalTimer.start()
cyclingActive = true
} else if (SessionData.wallpaperCyclingMode === "time") {
cyclingActive = true
checkTimeBasedCycling()
}
}
function stopCycling() {
intervalTimer.stop()
cyclingActive = false
}
function cycleToNextWallpaper() {
if (!SessionData.wallpaperPath) return
const wallpaperDir = SessionData.wallpaperPath.substring(0, SessionData.wallpaperPath.lastIndexOf('/'))
cyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
cyclingProcess.running = true
}
function cycleToPrevWallpaper() {
if (!SessionData.wallpaperPath) return
const wallpaperDir = SessionData.wallpaperPath.substring(0, SessionData.wallpaperPath.lastIndexOf('/'))
prevCyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
prevCyclingProcess.running = true
}
function cycleNextManually() {
if (SessionData.wallpaperPath) {
cycleToNextWallpaper()
// Restart timers if cycling is active
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
if (SessionData.wallpaperCyclingMode === "interval") {
intervalTimer.interval = cachedCyclingInterval * 1000
intervalTimer.restart()
}
}
}
}
function cyclePrevManually() {
if (SessionData.wallpaperPath) {
cycleToPrevWallpaper()
// Restart timers if cycling is active
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
if (SessionData.wallpaperCyclingMode === "interval") {
intervalTimer.interval = cachedCyclingInterval * 1000
intervalTimer.restart()
}
}
}
}
function checkTimeBasedCycling() {
const currentTime = Qt.formatTime(systemClock.date, "hh:mm")
if (currentTime === cachedCyclingTime && currentTime !== lastTimeCheck) {
lastTimeCheck = currentTime
cycleToNextWallpaper()
} else if (currentTime !== cachedCyclingTime) {
lastTimeCheck = ""
}
}
Timer {
id: intervalTimer
interval: cachedCyclingInterval * 1000
running: false
repeat: true
onTriggered: cycleToNextWallpaper()
}
SystemClock {
id: systemClock
precision: SystemClock.Minutes
onDateChanged: {
if (SessionData.wallpaperCyclingMode === "time" && cyclingActive) {
checkTimeBasedCycling()
}
}
}
Process {
id: cyclingProcess
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text && text.trim()) {
const files = text.trim().split('\n').filter(file => file.length > 0)
if (files.length <= 1) return
const wallpaperList = files.sort()
const currentPath = SessionData.wallpaperPath
let currentIndex = wallpaperList.findIndex(path => path === currentPath)
if (currentIndex === -1) currentIndex = 0
// Get next wallpaper
const nextIndex = (currentIndex + 1) % wallpaperList.length
const nextWallpaper = wallpaperList[nextIndex]
if (nextWallpaper && nextWallpaper !== currentPath) {
SessionData.setWallpaper(nextWallpaper)
}
}
}
}
}
Process {
id: prevCyclingProcess
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text && text.trim()) {
const files = text.trim().split('\n').filter(file => file.length > 0)
if (files.length <= 1) return
const wallpaperList = files.sort()
const currentPath = SessionData.wallpaperPath
let currentIndex = wallpaperList.findIndex(path => path === currentPath)
if (currentIndex === -1) currentIndex = 0
// Get previous wallpaper
const prevIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1
const prevWallpaper = wallpaperList[prevIndex]
if (prevWallpaper && prevWallpaper !== currentPath) {
SessionData.setWallpaper(prevWallpaper)
}
}
}
}
}
}

View File

@@ -28,9 +28,10 @@ Item {
Rectangle {
property int tabCount: tabRepeater.count
property bool isActive: tabBar.currentIndex === index
property bool hasIcon: tabBar.showIcons && modelData.icon
&& modelData.icon.length > 0
property bool hasText: modelData.text && modelData.text.length > 0
property bool hasIcon: tabBar.showIcons && modelData && ("icon" in modelData)
&& modelData.icon && modelData.icon.length > 0
property bool hasText: modelData && ("text" in modelData)
&& modelData.text && modelData.text.length > 0
width: tabBar.equalWidthTabs ? (tabBar.width - tabBar.spacing * (tabCount - 1))
/ tabCount : contentRow.implicitWidth + Theme.spacingM * 2