dots-hyprland/.config/quickshell/ii/services/Wallpapers.qml

172 lines
6.3 KiB
QML

import qs.modules.common
import qs.modules.common.models
import qs.modules.common.functions
import QtQuick
import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
/**
* Provides a list of wallpapers and an "apply" action that calls the existing
* switchwall.sh script. Pretty much a limited file browsing service.
*/
Singleton {
id: root
property string thumbgenScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/thumbgen.py`
property string generateThumbnailsMagicScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/generate-thumbnails-magick.sh`
property alias directory: folderModel.folder
readonly property string effectiveDirectory: FileUtils.trimFileProtocol(folderModel.folder.toString())
property url defaultFolder: Qt.resolvedUrl(`${Directories.pictures}/Wallpapers`)
property alias folderModel: folderModel // Expose for direct binding when needed
property string searchQuery: ""
readonly property list<string> extensions: [ // TODO: add videos
"jpg", "jpeg", "png", "webp", "avif", "bmp", "svg"
]
property list<string> wallpapers: [] // List of absolute file paths (without file://)
readonly property bool thumbnailGenerationRunning: thumbgenProc.running
property real thumbnailGenerationProgress: 0
signal changed()
signal thumbnailGenerated(directory: string)
signal thumbnailGeneratedFile(filePath: string)
// Executions
Process {
id: applyProc
}
function openFallbackPicker(darkMode = Appearance.m3colors.darkmode) {
applyProc.exec([
Directories.wallpaperSwitchScriptPath,
"--mode", (darkMode ? "dark" : "light")
])
}
function apply(path, darkMode = Appearance.m3colors.darkmode) {
if (!path || path.length === 0) return
applyProc.exec([
Directories.wallpaperSwitchScriptPath,
"--image", path,
"--mode", (darkMode ? "dark" : "light")
])
root.changed()
}
Process {
id: selectProc
property string filePath: ""
property bool darkMode: Appearance.m3colors.darkmode
function select(filePath, darkMode = Appearance.m3colors.darkmode) {
selectProc.filePath = filePath
selectProc.darkMode = darkMode
selectProc.exec(["test", "-d", FileUtils.trimFileProtocol(filePath)])
}
onExited: (exitCode, exitStatus) => {
if (exitCode === 0) {
setDirectory(selectProc.filePath);
return;
}
root.apply(selectProc.filePath, selectProc.darkMode);
}
}
function select(filePath, darkMode = Appearance.m3colors.darkmode) {
selectProc.select(filePath, darkMode);
}
Process {
id: validateDirProc
property string nicePath: ""
function setDirectoryIfValid(path) {
validateDirProc.nicePath = FileUtils.trimFileProtocol(path).replace(/\/+$/, "")
if (/^\/*$/.test(validateDirProc.nicePath)) validateDirProc.nicePath = "/";
validateDirProc.exec([
"bash", "-c",
`if [ -d "${validateDirProc.nicePath}" ]; then echo dir; elif [ -f "${validateDirProc.nicePath}" ]; then echo file; else echo invalid; fi`
])
}
stdout: StdioCollector {
onStreamFinished: {
root.directory = Qt.resolvedUrl(validateDirProc.nicePath)
const result = text.trim()
if (result === "dir") {
} else if (result === "file") {
root.directory = Qt.resolvedUrl(FileUtils.parentDirectory(validateDirProc.nicePath))
} else {
// Ignore
}
}
}
}
function setDirectory(path) {
validateDirProc.setDirectoryIfValid(path)
}
function navigateUp() {
folderModel.navigateUp()
}
function navigateBack() {
folderModel.navigateBack()
}
function navigateForward() {
folderModel.navigateForward()
}
// Folder model
FolderListModelWithHistory {
id: folderModel
folder: Qt.resolvedUrl(root.defaultFolder)
caseSensitive: false
nameFilters: root.extensions.map(ext => `*${searchQuery.split(" ").filter(s => s.length > 0).map(s => `*${s}*`)}*.${ext}`)
showDirs: true
showDotAndDotDot: false
showOnlyReadable: true
sortField: FolderListModel.Time
sortReversed: false
onCountChanged: {
root.wallpapers = []
for (let i = 0; i < folderModel.count; i++) {
const path = folderModel.get(i, "filePath") || FileUtils.trimFileProtocol(folderModel.get(i, "fileURL"))
if (path && path.length) root.wallpapers.push(path)
}
}
}
// Thumbnail generation
function generateThumbnail(size: string) {
if (!["normal", "large", "x-large", "xx-large"].includes(size)) throw new Error("Invalid thumbnail size");
thumbgenProc.directory = root.directory
thumbgenProc.running = false
thumbgenProc.command = [
"bash", "-c",
`${thumbgenScriptPath} --size ${size} --machine_progress -d ${root.directory} || ${generateThumbnailsMagicScriptPath} --size ${size} -d ${root.directory}`,
]
root.thumbnailGenerationProgress = 0
thumbgenProc.running = true
}
Process {
id: thumbgenProc
property string directory
stdout: SplitParser {
onRead: data => {
// print("thumb gen proc:", data)
let match = data.match(/PROGRESS (\d+)\/(\d+)/)
if (match) {
const completed = parseInt(match[1])
const total = parseInt(match[2])
root.thumbnailGenerationProgress = completed / total
}
match = data.match(/FILE (.+)/)
if (match) {
const filePath = match[1]
root.thumbnailGeneratedFile(filePath)
}
}
}
onExited: (exitCode, exitStatus) => {
root.thumbnailGenerated(thumbgenProc.directory)
}
}
}