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:
parent
5ab76c98e5
commit
299add4a15
19 changed files with 1232 additions and 799 deletions
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, "'\\''")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue