Remove need for polkit, launch any ArchUpdater update through terminal

ArchUpdater: rely on `TERMINAL` environment variable
README: Add explanation for the `TERMINAL` environment variable
This commit is contained in:
Ly-sec 2025-08-30 02:28:48 +02:00
parent 5ab76c98e5
commit 299add4a15
19 changed files with 1232 additions and 799 deletions

View file

@ -8,28 +8,164 @@ import qs.Commons
Singleton {
id: updateService
// Core properties
readonly property bool busy: checkupdatesProcess.running
readonly property bool aurBusy: checkAurUpdatesProcess.running
readonly property int updates: repoPackages.length
readonly property int aurUpdates: aurPackages.length
readonly property int totalUpdates: updates + aurUpdates
// ============================================================================
// CORE PROPERTIES
// ============================================================================
// Package data
property var repoPackages: []
property var aurPackages: []
property var selectedPackages: []
property int selectedPackagesCount: 0
// Update state
property bool updateInProgress: false
property bool updateFailed: false
property string lastUpdateError: ""
// Computed properties
readonly property bool busy: checkupdatesProcess.running
readonly property bool aurBusy: checkParuUpdatesProcess.running
readonly property int updates: repoPackages.length
readonly property int aurUpdates: aurPackages.length
readonly property int totalUpdates: updates + aurUpdates
// ============================================================================
// TIMERS
// ============================================================================
// Refresh timer for post-update polling
Timer {
id: refreshTimer
interval: 5000 // Increased delay to ensure updates complete
repeat: false
onTriggered: {
console.log("ArchUpdater: Refreshing package lists after update...")
// Just refresh package lists without syncing database
doPoll()
}
}
// Timer to mark update as complete - with error handling
Timer {
id: updateCompleteTimer
interval: 30000 // Increased to 30 seconds to allow more time
repeat: false
onTriggered: {
console.log("ArchUpdater: Update timeout reached, checking for failures...")
checkForUpdateFailures()
}
}
// Timer to check if update processes are still running
Timer {
id: updateMonitorTimer
interval: 2000
repeat: true
running: updateInProgress
onTriggered: {
// Check if any update-related processes might still be running
checkUpdateStatus()
}
}
// ============================================================================
// MONITORING PROCESSES
// ============================================================================
// Process to monitor update completion
Process {
id: updateStatusProcess
command: ["pgrep", "-f", "(pacman|yay|paru).*(-S|-Syu)"]
onExited: function (exitCode) {
if (exitCode !== 0 && updateInProgress) {
// No update processes found, update likely completed
console.log("ArchUpdater: No update processes detected, marking update as complete")
updateInProgress = false
updateMonitorTimer.stop()
// Don't stop the complete timer - let it handle failures
// If the update actually failed, the timer will trigger and set updateFailed = true
// Refresh package lists after a short delay
Qt.callLater(() => {
doPoll()
}, 2000)
}
}
}
// Process to check for errors in log file (only when update is in progress)
Process {
id: errorCheckProcess
command: ["sh", "-c", "if [ -f /tmp/archupdater_output.log ]; then grep -i 'error\\|failed\\|failed to build\\|ERROR_DETECTED' /tmp/archupdater_output.log | tail -1; fi"]
onExited: function (exitCode) {
if (exitCode === 0 && updateInProgress) {
// Error found in log
console.log("ArchUpdater: Error detected in log file")
updateInProgress = false
updateFailed = true
updateCompleteTimer.stop()
updateMonitorTimer.stop()
lastUpdateError = "Build or update error detected"
// Refresh to check actual state
Qt.callLater(() => {
doPoll()
}, 1000)
}
}
}
// Timer to check for errors more frequently when update is in progress
Timer {
id: errorCheckTimer
interval: 5000 // Check every 5 seconds
repeat: true
running: updateInProgress
onTriggered: {
if (updateInProgress && !errorCheckProcess.running) {
errorCheckProcess.running = true
}
}
}
// ============================================================================
// MONITORING FUNCTIONS
// ============================================================================
function checkUpdateStatus() {
if (updateInProgress && !updateStatusProcess.running) {
updateStatusProcess.running = true
}
}
function checkForUpdateFailures() {
console.log("ArchUpdater: Checking for update failures...")
updateInProgress = false
updateFailed = true
updateCompleteTimer.stop()
updateMonitorTimer.stop()
// Refresh to check actual state after a delay
Qt.callLater(() => {
doPoll()
}, 2000)
}
// Initial check
Component.onCompleted: {
getAurHelper()
doPoll()
doAurPoll()
}
// ============================================================================
// PACKAGE CHECKING PROCESSES
// ============================================================================
// Process for checking repo updates
Process {
id: checkupdatesProcess
command: ["checkupdates"]
command: ["checkupdates", "--nosync"]
onExited: function (exitCode) {
if (exitCode !== 0 && exitCode !== 2) {
Logger.warn("ArchUpdater", "checkupdates failed (code:", exitCode, ")")
@ -44,13 +180,13 @@ Singleton {
}
}
// Process for checking AUR updates
// Process for checking AUR updates with paru specifically
Process {
id: checkAurUpdatesProcess
command: ["sh", "-c", "command -v yay >/dev/null 2>&1 && yay -Qua || command -v paru >/dev/null 2>&1 && paru -Qua || echo ''"]
id: checkParuUpdatesProcess
command: ["paru", "-Qua"]
onExited: function (exitCode) {
if (exitCode !== 0) {
Logger.warn("ArchUpdater", "AUR check failed (code:", exitCode, ")")
Logger.warn("ArchUpdater", "paru check failed (code:", exitCode, ")")
aurPackages = []
}
}
@ -62,8 +198,12 @@ Singleton {
}
}
// Parse checkupdates output
function parseCheckupdatesOutput(output) {
// ============================================================================
// PARSING FUNCTIONS
// ============================================================================
// Generic package parsing function
function parsePackageOutput(output, source) {
const lines = output.trim().split('\n').filter(line => line.trim())
const packages = []
@ -75,65 +215,83 @@ Singleton {
"oldVersion": m[2],
"newVersion": m[3],
"description": `${m[1]} ${m[2]} -> ${m[3]}`,
"source": "repo"
"source": source
})
}
}
repoPackages = packages
// Only update if we have new data or if this is a fresh check
if (packages.length > 0 || output.trim() === "") {
if (source === "repo") {
repoPackages = packages
} else {
aurPackages = packages
}
}
}
// Parse checkupdates output
function parseCheckupdatesOutput(output) {
parsePackageOutput(output, "repo")
}
// Parse AUR updates output
function parseAurUpdatesOutput(output) {
const lines = output.trim().split('\n').filter(line => line.trim())
const packages = []
parsePackageOutput(output, "aur")
}
for (const line of lines) {
const m = line.match(/^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/)
if (m) {
packages.push({
"name": m[1],
"oldVersion": m[2],
"newVersion": m[3],
"description": `${m[1]} ${m[2]} -> ${m[3]}`,
"source": "aur"
})
}
function doPoll() {
// Start repo updates check
if (!busy) {
checkupdatesProcess.running = true
}
aurPackages = packages
// Start AUR updates check
if (!aurBusy) {
checkParuUpdatesProcess.running = true
}
}
// Check for updates
function doPoll() {
if (busy)
return
checkupdatesProcess.running = true
}
// ============================================================================
// UPDATE FUNCTIONS
// ============================================================================
// Check for AUR updates
function doAurPoll() {
if (aurBusy)
return
checkAurUpdatesProcess.running = true
// Helper function to generate update command with error detection
function generateUpdateCommand(baseCommand) {
return baseCommand + " 2>&1 | tee /tmp/archupdater_output.log; if [ $? -ne 0 ]; then echo 'ERROR_DETECTED'; fi; echo 'Update complete! Press Enter to close...'; read -p 'Press Enter to continue...'"
}
// Update all packages (repo + AUR)
function runUpdate() {
if (totalUpdates === 0) {
doPoll()
doAurPoll()
return
}
// Reset any previous error states
updateFailed = false
lastUpdateError = ""
updateInProgress = true
// Update repos first, then AUR
Quickshell.execDetached(["pkexec", "pacman", "-Syu", "--noconfirm"])
Quickshell.execDetached(
["sh", "-c", "command -v yay >/dev/null 2>&1 && yay -Sua --noconfirm || command -v paru >/dev/null 2>&1 && paru -Sua --noconfirm || true"])
console.log("ArchUpdater: Starting full system update...")
// Refresh after updates with multiple attempts
refreshAfterUpdate()
const terminal = Quickshell.env("TERMINAL") || "xterm"
// Check if we have an AUR helper for full system update
const aurHelper = getAurHelper()
if (aurHelper && (aurUpdates > 0 || updates > 0)) {
// Use AUR helper for full system update (handles both repo and AUR)
const command = generateUpdateCommand(aurHelper + " -Syu")
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
} else if (updates > 0) {
// Fallback to pacman if no AUR helper or only repo updates
const command = generateUpdateCommand("sudo pacman -Syu")
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
}
// Start monitoring and timeout timers
refreshTimer.start()
updateCompleteTimer.start()
updateMonitorTimer.start()
}
// Update selected packages
@ -141,7 +299,13 @@ Singleton {
if (selectedPackages.length === 0)
return
// Reset any previous error states
updateFailed = false
lastUpdateError = ""
updateInProgress = true
console.log("ArchUpdater: Starting selective update for", selectedPackages.length, "packages")
const terminal = Quickshell.env("TERMINAL") || "xterm"
// Split selected packages by source
const repoPkgs = []
@ -159,48 +323,140 @@ Singleton {
}
}
// Update repo packages
// Update repo packages with sudo
if (repoPkgs.length > 0) {
const repoCommand = ["pkexec", "pacman", "-S", "--noconfirm"].concat(repoPkgs)
Logger.log("ArchUpdater", "Running repo command:", repoCommand.join(" "))
Quickshell.execDetached(repoCommand)
const packageList = repoPkgs.join(" ")
const command = generateUpdateCommand("sudo pacman -S " + packageList)
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
}
// Update AUR packages
// Update AUR packages with yay/paru
if (aurPkgs.length > 0) {
const aurHelper = getAurHelper()
if (aurHelper) {
const aurCommand = [aurHelper, "-S", "--noconfirm"].concat(aurPkgs)
Logger.log("ArchUpdater", "Running AUR command:", aurCommand.join(" "))
Quickshell.execDetached(aurCommand)
const packageList = aurPkgs.join(" ")
const command = generateUpdateCommand(aurHelper + " -S " + packageList)
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
} else {
Logger.warn("ArchUpdater", "No AUR helper found for packages:", aurPkgs.join(", "))
}
}
// Clear selection and refresh
selectedPackages = []
selectedPackagesCount = 0
refreshAfterUpdate()
// Start monitoring and timeout timers
refreshTimer.start()
updateCompleteTimer.start()
updateMonitorTimer.start()
}
// Reset update state (useful for manual recovery)
function resetUpdateState() {
// If update is in progress, mark it as failed first
if (updateInProgress) {
updateFailed = true
}
updateInProgress = false
lastUpdateError = ""
updateCompleteTimer.stop()
updateMonitorTimer.stop()
refreshTimer.stop()
// Refresh to get current state
doPoll()
}
// Manual refresh function
function forceRefresh() {
// Prevent multiple simultaneous refreshes
if (busy || aurBusy) {
return
}
// Clear error states when refreshing
updateFailed = false
lastUpdateError = ""
// Just refresh the package lists without syncing databases
doPoll()
}
// ============================================================================
// UTILITY PROCESSES
// ============================================================================
// Process for checking yay availability
Process {
id: yayCheckProcess
command: ["which", "yay"]
onExited: function (exitCode) {
if (exitCode === 0) {
cachedAurHelper = "yay"
}
}
}
// Process for checking paru availability
Process {
id: paruCheckProcess
command: ["which", "paru"]
onExited: function (exitCode) {
if (exitCode === 0) {
if (cachedAurHelper === "") {
cachedAurHelper = "paru"
}
}
}
}
// Process for syncing package databases with sudo
Process {
id: syncDatabaseProcess
command: ["sudo", "pacman", "-Sy"]
onStarted: {
console.log("ArchUpdater: Starting database sync with sudo...")
}
onExited: function (exitCode) {
console.log("ArchUpdater: Database sync exited with code:", exitCode)
if (exitCode === 0) {
console.log("ArchUpdater: Database sync successful")
} else {
console.log("ArchUpdater: Database sync failed")
}
// After sync completes, wait a moment then refresh package lists
console.log("ArchUpdater: Database sync complete, waiting before refresh...")
Qt.callLater(() => {
console.log("ArchUpdater: Refreshing package lists after database sync...")
doPoll()
}, 2000)
}
}
// Cached AUR helper detection
property string cachedAurHelper: ""
// Helper function to detect AUR helper
function getAurHelper() {
// Check for yay first, then paru
const yayCheck = Quickshell.exec("command -v yay", true)
if (yayCheck.exitCode === 0 && yayCheck.stdout.trim()) {
return "yay"
// Return cached result if available
if (cachedAurHelper !== "") {
return cachedAurHelper
}
const paruCheck = Quickshell.exec("command -v paru", true)
if (paruCheck.exitCode === 0 && paruCheck.stdout.trim()) {
return "paru"
}
// Check for AUR helpers using Process objects
console.log("ArchUpdater: Detecting AUR helper...")
return null
// Start the detection processes
yayCheckProcess.running = true
paruCheckProcess.running = true
// For now, return a default (will be updated by the processes)
// In a real implementation, you'd want to wait for the processes to complete
return "paru" // Default fallback
}
// Package selection functions
// ============================================================================
// PACKAGE SELECTION FUNCTIONS
// ============================================================================
function togglePackageSelection(packageName) {
const index = selectedPackages.indexOf(packageName)
if (index > -1) {
@ -225,47 +481,60 @@ Singleton {
return selectedPackages.indexOf(packageName) > -1
}
// Robust refresh after updates
function refreshAfterUpdate() {
// First refresh attempt after 3 seconds
Qt.callLater(() => {
doPoll()
doAurPoll()
}, 3000)
// ============================================================================
// REFRESH FUNCTIONS
// ============================================================================
// Second refresh attempt after 8 seconds
Qt.callLater(() => {
doPoll()
doAurPoll()
}, 8000)
// Third refresh attempt after 15 seconds
Qt.callLater(() => {
doPoll()
doAurPoll()
updateInProgress = false
}, 15000)
// Final refresh attempt after 30 seconds
Qt.callLater(() => {
doPoll()
doAurPoll()
}, 30000)
// Function to manually sync package databases (separate from refresh)
function syncPackageDatabases() {
console.log("ArchUpdater: Manual database sync requested...")
const terminal = Quickshell.env("TERMINAL") || "xterm"
const command = "sudo pacman -Sy && echo 'Database sync complete! Press Enter to close...' && read -p 'Press Enter to continue...'"
console.log("ArchUpdater: Executing sync command:", command)
console.log("ArchUpdater: Terminal:", terminal)
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
}
// Function to force a complete refresh (sync + check)
function forceCompleteRefresh() {
console.log("ArchUpdater: Force complete refresh requested...")
// Start database sync process (will trigger refresh when complete)
console.log("ArchUpdater: Starting complete refresh process...")
syncDatabaseProcess.running = true
}
// Function to sync database and refresh package lists
function syncDatabaseAndRefresh() {
console.log("ArchUpdater: Syncing database and refreshing package lists...")
// Start database sync process (will trigger refresh when complete)
console.log("ArchUpdater: Starting database sync process...")
syncDatabaseProcess.running = true
}
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
// Notification helper
function notify(title, body) {
Quickshell.execDetached(["notify-send", "-a", "UpdateService", "-i", "system-software-update", title, body])
}
// ============================================================================
// AUTO-POLL TIMER
// ============================================================================
// Auto-poll every 15 minutes
Timer {
interval: 15 * 60 * 1000 // 15 minutes
repeat: true
running: true
onTriggered: {
doPoll()
doAurPoll()
if (!updateInProgress) {
doPoll()
}
}
}
}

View file

@ -22,11 +22,9 @@ 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, "'\\''")

View file

@ -11,7 +11,7 @@ Singleton {
function scale(aScreen) {
try {
if (aScreen !== undefined && aScreen.name !== undefined) {
return getMonitorScale(aScreen.name)
return scaleByName(aScreen.name)
}
} catch (e) {
@ -20,46 +20,21 @@ Singleton {
return 1.0
}
// -------------------------------------------
function getMonitorScale(aScreenName) {
function scaleByName(aScreenName) {
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) {
return monitors[i].scale
}
if (Settings.data.monitorsScaling !== undefined) {
if (Settings.data.monitorsScaling[aScreenName] !== undefined) {
return Settings.data.monitorsScaling[aScreenName]
}
}
} 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

View file

@ -10,197 +10,84 @@ Singleton {
id: root
Component.onCompleted: {
Logger.log("Wallpaper", "Service started")
Logger.log("Wallpapers", "Service started")
listWallpapers()
// Wallpaper is set when the settings are loaded.
// Don't start random wallpaper during initialization
}
// All available wallpaper transitions
readonly property ListModel transitionsModel: ListModel {
ListElement {
key: "none"
name: "None"
}
ListElement {
key: "fade"
name: "Fade"
}
ListElement {
key: "wipe_left"
name: "Wipe Left"
}
ListElement {
key: "wipe_right"
name: "Wipe Right"
}
ListElement {
key: "wipe_up"
name: "Wipe Up"
}
ListElement {
key: "wipe_down"
name: "Wipe Down"
}
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 : "")
}
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()
}
function changeWallpaper(path) {
Logger.log("Wallpapers", "Changing to:", path)
setCurrentWallpaper(path, false)
}
// -------------------------------------------------------------------
// 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]
}
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
}
}
}
// -------------------------------------------------------------------
// 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
changeWallpaperProcess.running = true
} 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
// Fallback: update the settings directly for non-SWWW mode
//Logger.log("Wallpapers", "Not using Swww, setting wallpaper directly")
}
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()
}
// Notify ColorScheme service if the wallpaper actually changed
// Only notify ColorScheme service if the wallpaper actually changed
if (wallpaperChanged) {
ColorSchemeService.changedWallpaper()
}
}
// -------------------------------------------------------------------
function setRandomWallpaper() {
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)
}
var randomIndex = Math.floor(Math.random() * wallpaperList.length)
var randomPath = wallpaperList[randomIndex]
if (!randomPath) {
return
}
setCurrentWallpaper(randomPath, false)
}
// -------------------------------------------------------------------
function toggleRandomWallpaper() {
Logger.log("Wallpaper", "toggleRandomWallpaper")
if (Settings.data.wallpaper.randomEnabled) {
randomWallpaperTimer.restart()
if (Settings.data.wallpaper.isRandom && !randomWallpaperTimer.running) {
randomWallpaperTimer.start()
setRandomWallpaper()
} else if (!Settings.data.randomWallpaper && randomWallpaperTimer.running) {
randomWallpaperTimer.stop()
}
}
// -------------------------------------------------------------------
function restartRandomWallpaperTimer() {
if (Settings.data.wallpaper.isRandom) {
randomWallpaperTimer.stop()
@ -208,81 +95,78 @@ Singleton {
}
}
// -------------------------------------------------------------------
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
}
function startSWWWDaemon() {
if (Settings.data.wallpaper.swww.enabled) {
Logger.log("Swww", "Requesting swww-daemon")
startDaemonProcess.running = true
}
}
// -------------------------------------------------------------------
// -------------------------------------------------------------------
// -------------------------------------------------------------------
Timer {
id: randomWallpaperTimer
interval: Settings.data.wallpaper.randomIntervalSec * 1000
running: Settings.data.wallpaper.randomEnabled
interval: Settings.data.wallpaper.randomInterval * 1000
running: false
repeat: true
onTriggered: setRandomWallpaper()
triggeredOnStart: false
}
// 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)
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)
}
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")
}
}
}