From a21a846bf590178df447f2a080424144b3a1b573 Mon Sep 17 00:00:00 2001 From: bbedward Date: Thu, 8 Jan 2026 14:31:48 -0500 Subject: [PATCH] wallpaper: encode image URIs fixes #1306 --- .../Modals/FileBrowser/FileBrowserContent.qml | 10 ++- .../FileBrowser/FileBrowserGridDelegate.qml | 72 +++++++++---------- .../FileBrowser/FileBrowserListDelegate.qml | 68 +++++++++--------- .../Modules/BlurredWallpaperBackground.qml | 10 ++- quickshell/Modules/DankDash/WallpaperTab.qml | 7 +- quickshell/Modules/Greetd/GreeterContent.qml | 20 +++--- quickshell/Modules/Lock/LockScreenContent.qml | 18 ++--- .../Modules/Settings/ThemeColorsTab.qml | 2 +- quickshell/Modules/Settings/WallpaperTab.qml | 24 +++---- quickshell/Modules/WallpaperBackground.qml | 10 ++- quickshell/Widgets/CachingImage.qml | 7 +- 11 files changed, 131 insertions(+), 117 deletions(-) diff --git a/quickshell/Modals/FileBrowser/FileBrowserContent.qml b/quickshell/Modals/FileBrowser/FileBrowserContent.qml index 0e094ad7..c36b54a8 100644 --- a/quickshell/Modals/FileBrowser/FileBrowserContent.qml +++ b/quickshell/Modals/FileBrowser/FileBrowserContent.qml @@ -52,6 +52,12 @@ FocusScope { signal fileSelected(string path) signal closeRequested + function encodeFileUrl(path) { + if (!path) + return ""; + return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/'); + } + function initialize() { loadSettings(); currentPath = getLastPath(); @@ -188,7 +194,7 @@ FocusScope { function handleSaveFile(filePath) { var normalizedPath = filePath; if (!normalizedPath.startsWith("file://")) { - normalizedPath = "file://" + filePath; + normalizedPath = encodeFileUrl(filePath); } var exists = false; @@ -274,7 +280,7 @@ FocusScope { nameFilters: fileExtensions showFiles: true showDirs: true - folder: currentPath ? "file://" + currentPath : "file://" + homeDir + folder: encodeFileUrl(currentPath || homeDir) sortField: { switch (sortBy) { case "name": diff --git a/quickshell/Modals/FileBrowser/FileBrowserGridDelegate.qml b/quickshell/Modals/FileBrowser/FileBrowserGridDelegate.qml index eb22049b..dbc594c0 100644 --- a/quickshell/Modals/FileBrowser/FileBrowserGridDelegate.qml +++ b/quickshell/Modals/FileBrowser/FileBrowserGridDelegate.qml @@ -21,67 +21,67 @@ StyledRect { signal itemSelected(int index, string path, string name, bool isDir) function getFileExtension(fileName) { - const parts = fileName.split('.') + const parts = fileName.split('.'); if (parts.length > 1) { - return parts[parts.length - 1].toLowerCase() + return parts[parts.length - 1].toLowerCase(); } - return "" + return ""; } function determineFileType(fileName) { - const ext = getFileExtension(fileName) + const ext = getFileExtension(fileName); - const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"] + const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"]; if (imageExts.includes(ext)) { - return "image" + return "image"; } - const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"] + const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"]; if (videoExts.includes(ext)) { - return "video" + return "video"; } - const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"] + const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]; if (audioExts.includes(ext)) { - return "audio" + return "audio"; } - const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"] + const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"]; if (codeExts.includes(ext)) { - return "code" + return "code"; } - const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"] + const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"]; if (docExts.includes(ext)) { - return "document" + return "document"; } - const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"] + const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"]; if (archiveExts.includes(ext)) { - return "archive" + return "archive"; } if (!ext || fileName.indexOf('.') === -1) { - return "binary" + return "binary"; } - return "file" + return "file"; } function isImageFile(fileName) { if (!fileName) { - return false + return false; } - return determineFileType(fileName) === "image" + return determineFileType(fileName) === "image"; } function getIconForFile(fileName) { - const lowerName = fileName.toLowerCase() + const lowerName = fileName.toLowerCase(); if (lowerName.startsWith("dockerfile")) { - return "docker" + return "docker"; } - const ext = fileName.split('.').pop() - return ext || "" + const ext = fileName.split('.').pop(); + return ext || ""; } width: weMode ? 245 : iconSizes[iconSizeIndex] + 16 @@ -89,21 +89,21 @@ StyledRect { radius: Theme.cornerRadius color: { if (keyboardNavigationActive && delegateRoot.index === selectedIndex) - return Theme.surfacePressed + return Theme.surfacePressed; - return mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent" + return mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"; } border.color: keyboardNavigationActive && delegateRoot.index === selectedIndex ? Theme.primary : "transparent" border.width: (keyboardNavigationActive && delegateRoot.index === selectedIndex) ? 2 : 0 Component.onCompleted: { if (keyboardNavigationActive && delegateRoot.index === selectedIndex) - itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir) + itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir); } onSelectedIndexChanged: { if (keyboardNavigationActive && selectedIndex === delegateRoot.index) - itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir) + itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir); } Column { @@ -121,19 +121,17 @@ StyledRect { anchors.margins: 2 property var weExtensions: [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tga"] property int weExtIndex: 0 - source: { - if (weMode && delegateRoot.fileIsDir) { - return "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex] - } - return (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? ("file://" + delegateRoot.filePath) : "" + imagePath: { + if (weMode && delegateRoot.fileIsDir) + return delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]; + return (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? delegateRoot.filePath : ""; } onStatusChanged: { if (weMode && delegateRoot.fileIsDir && status === Image.Error) { if (weExtIndex < weExtensions.length - 1) { - weExtIndex++ - source = "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex] + weExtIndex++; } else { - source = "" + imagePath = ""; } } } @@ -198,7 +196,7 @@ StyledRect { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - itemClicked(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir) + itemClicked(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir); } } } diff --git a/quickshell/Modals/FileBrowser/FileBrowserListDelegate.qml b/quickshell/Modals/FileBrowser/FileBrowserListDelegate.qml index ce92607a..fd3e22a2 100644 --- a/quickshell/Modals/FileBrowser/FileBrowserListDelegate.qml +++ b/quickshell/Modals/FileBrowser/FileBrowserListDelegate.qml @@ -20,97 +20,97 @@ StyledRect { signal itemSelected(int index, string path, string name, bool isDir) function getFileExtension(fileName) { - const parts = fileName.split('.') + const parts = fileName.split('.'); if (parts.length > 1) { - return parts[parts.length - 1].toLowerCase() + return parts[parts.length - 1].toLowerCase(); } - return "" + return ""; } function determineFileType(fileName) { - const ext = getFileExtension(fileName) + const ext = getFileExtension(fileName); - const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"] + const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"]; if (imageExts.includes(ext)) { - return "image" + return "image"; } - const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"] + const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"]; if (videoExts.includes(ext)) { - return "video" + return "video"; } - const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"] + const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]; if (audioExts.includes(ext)) { - return "audio" + return "audio"; } - const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"] + const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"]; if (codeExts.includes(ext)) { - return "code" + return "code"; } - const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"] + const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"]; if (docExts.includes(ext)) { - return "document" + return "document"; } - const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"] + const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"]; if (archiveExts.includes(ext)) { - return "archive" + return "archive"; } if (!ext || fileName.indexOf('.') === -1) { - return "binary" + return "binary"; } - return "file" + return "file"; } function isImageFile(fileName) { if (!fileName) { - return false + return false; } - return determineFileType(fileName) === "image" + return determineFileType(fileName) === "image"; } function getIconForFile(fileName) { - const lowerName = fileName.toLowerCase() + const lowerName = fileName.toLowerCase(); if (lowerName.startsWith("dockerfile")) { - return "docker" + return "docker"; } - const ext = fileName.split('.').pop() - return ext || "" + const ext = fileName.split('.').pop(); + return ext || ""; } function formatFileSize(size) { if (size < 1024) - return size + " B" + return size + " B"; if (size < 1024 * 1024) - return (size / 1024).toFixed(1) + " KB" + return (size / 1024).toFixed(1) + " KB"; if (size < 1024 * 1024 * 1024) - return (size / (1024 * 1024)).toFixed(1) + " MB" - return (size / (1024 * 1024 * 1024)).toFixed(1) + " GB" + return (size / (1024 * 1024)).toFixed(1) + " MB"; + return (size / (1024 * 1024 * 1024)).toFixed(1) + " GB"; } height: 44 radius: Theme.cornerRadius color: { if (keyboardNavigationActive && listDelegateRoot.index === selectedIndex) - return Theme.surfacePressed - return listMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent" + return Theme.surfacePressed; + return listMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"; } border.color: keyboardNavigationActive && listDelegateRoot.index === selectedIndex ? Theme.primary : "transparent" border.width: (keyboardNavigationActive && listDelegateRoot.index === selectedIndex) ? 2 : 0 Component.onCompleted: { if (keyboardNavigationActive && listDelegateRoot.index === selectedIndex) - itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir) + itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir); } onSelectedIndexChanged: { if (keyboardNavigationActive && selectedIndex === listDelegateRoot.index) - itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir) + itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir); } Row { @@ -127,7 +127,7 @@ StyledRect { CachingImage { id: listPreviewImage anchors.fill: parent - source: (!listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)) ? ("file://" + listDelegateRoot.filePath) : "" + imagePath: (!listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)) ? listDelegateRoot.filePath : "" fillMode: Image.PreserveAspectCrop maxCacheSize: 32 visible: false @@ -203,7 +203,7 @@ StyledRect { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - itemClicked(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir) + itemClicked(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir); } } } diff --git a/quickshell/Modules/BlurredWallpaperBackground.qml b/quickshell/Modules/BlurredWallpaperBackground.qml index 57281165..43171e69 100644 --- a/quickshell/Modules/BlurredWallpaperBackground.qml +++ b/quickshell/Modules/BlurredWallpaperBackground.qml @@ -40,6 +40,12 @@ Variants { id: root anchors.fill: parent + function encodeFileUrl(path) { + if (!path) + return ""; + return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/'); + } + property string source: SessionData.getMonitorWallpaper(modelData.name) || "" property bool isColorSource: source.startsWith("#") @@ -83,7 +89,7 @@ Variants { isInitialized = true; return; } - const formattedSource = source.startsWith("file://") ? source : "file://" + source; + const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source); setWallpaperImmediate(formattedSource); isInitialized = true; } @@ -100,7 +106,7 @@ Variants { return; } - const formattedSource = source.startsWith("file://") ? source : "file://" + source; + const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source); if (!isInitialized || !currentWallpaper.source) { setWallpaperImmediate(formattedSource); diff --git a/quickshell/Modules/DankDash/WallpaperTab.qml b/quickshell/Modules/DankDash/WallpaperTab.qml index ce54ce51..0ec97e3f 100644 --- a/quickshell/Modules/DankDash/WallpaperTab.qml +++ b/quickshell/Modules/DankDash/WallpaperTab.qml @@ -1,7 +1,6 @@ import Qt.labs.folderlistmodel import QtCore import QtQuick -import QtQuick.Controls import QtQuick.Effects import qs.Common import qs.Modals.FileBrowser @@ -311,7 +310,7 @@ Item { showFiles: true showDirs: false sortField: FolderListModel.Name - folder: wallpaperDir ? "file://" + wallpaperDir : "" + folder: wallpaperDir ? "file://" + wallpaperDir.split('/').map(s => encodeURIComponent(s)).join('/') : "" } FileBrowserSurfaceModal { @@ -401,7 +400,9 @@ Item { currentIndex = clampedIndex; positionViewAtIndex(clampedIndex, GridView.Contain); } - Qt.callLater(() => { enableAnimation = true; }); + Qt.callLater(() => { + enableAnimation = true; + }); } Connections { diff --git a/quickshell/Modules/Greetd/GreeterContent.qml b/quickshell/Modules/Greetd/GreeterContent.qml index 6d7eae7c..7dd7a200 100644 --- a/quickshell/Modules/Greetd/GreeterContent.qml +++ b/quickshell/Modules/Greetd/GreeterContent.qml @@ -15,6 +15,12 @@ import qs.Modules.Lock Item { id: root + function encodeFileUrl(path) { + if (!path) + return ""; + return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/'); + } + readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS") property string screenName: "" property string randomFact: "" @@ -162,7 +168,7 @@ Item { var _ = SessionData.perMonitorWallpaper; var __ = SessionData.monitorWallpapers; var currentWallpaper = SessionData.getMonitorWallpaper(screenName); - return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : ""; + return (currentWallpaper && !currentWallpaper.startsWith("#")) ? encodeFileUrl(currentWallpaper) : ""; } fillMode: Theme.getFillMode(GreetdSettings.wallpaperFillMode) smooth: true @@ -356,14 +362,10 @@ Item { Layout.preferredWidth: 60 Layout.preferredHeight: 60 imageSource: { - if (PortalService.profileImage === "") { + if (PortalService.profileImage === "") return ""; - } - - if (PortalService.profileImage.startsWith("/")) { - return "file://" + PortalService.profileImage; - } - + if (PortalService.profileImage.startsWith("/")) + return encodeFileUrl(PortalService.profileImage); return PortalService.profileImage; } fallbackIcon: "person" @@ -1154,7 +1156,7 @@ Item { required property string modelData FolderListModel { - folder: "file://" + modelData + folder: encodeFileUrl(modelData) nameFilters: ["*.desktop"] showDirs: false showDotAndDotDot: false diff --git a/quickshell/Modules/Lock/LockScreenContent.qml b/quickshell/Modules/Lock/LockScreenContent.qml index 7ab86244..cbb01360 100644 --- a/quickshell/Modules/Lock/LockScreenContent.qml +++ b/quickshell/Modules/Lock/LockScreenContent.qml @@ -14,6 +14,12 @@ import qs.Widgets Item { id: root + function encodeFileUrl(path) { + if (!path) + return ""; + return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/'); + } + property string passwordBuffer: "" property bool demoMode: false property string screenName: "" @@ -170,7 +176,7 @@ Item { anchors.fill: parent source: { var currentWallpaper = SessionData.getMonitorWallpaper(screenName); - return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : ""; + return (currentWallpaper && !currentWallpaper.startsWith("#")) ? encodeFileUrl(currentWallpaper) : ""; } fillMode: Theme.getFillMode(SettingsData.wallpaperFillMode) smooth: true @@ -659,14 +665,10 @@ Item { Layout.preferredWidth: 60 Layout.preferredHeight: 60 imageSource: { - if (PortalService.profileImage === "") { + if (PortalService.profileImage === "") return ""; - } - - if (PortalService.profileImage.startsWith("/")) { - return "file://" + PortalService.profileImage; - } - + if (PortalService.profileImage.startsWith("/")) + return encodeFileUrl(PortalService.profileImage); return PortalService.profileImage; } fallbackIcon: "person" diff --git a/quickshell/Modules/Settings/ThemeColorsTab.qml b/quickshell/Modules/Settings/ThemeColorsTab.qml index df839f3e..51192c11 100644 --- a/quickshell/Modules/Settings/ThemeColorsTab.qml +++ b/quickshell/Modules/Settings/ThemeColorsTab.qml @@ -389,7 +389,7 @@ Item { CachingImage { anchors.fill: parent anchors.margins: 1 - source: Theme.wallpaperPath ? "file://" + Theme.wallpaperPath : "" + imagePath: (Theme.wallpaperPath && !Theme.wallpaperPath.startsWith("#")) ? Theme.wallpaperPath : "" fillMode: Image.PreserveAspectCrop visible: Theme.wallpaperPath && !Theme.wallpaperPath.startsWith("#") layer.enabled: true diff --git a/quickshell/Modules/Settings/WallpaperTab.qml b/quickshell/Modules/Settings/WallpaperTab.qml index 35b0dff2..00734ee5 100644 --- a/quickshell/Modules/Settings/WallpaperTab.qml +++ b/quickshell/Modules/Settings/WallpaperTab.qml @@ -55,9 +55,9 @@ Item { CachingImage { anchors.fill: parent anchors.margins: 1 - source: { + imagePath: { var currentWallpaper = SessionData.perMonitorWallpaper ? SessionData.getMonitorWallpaper(selectedMonitorName) : SessionData.wallpaperPath; - return (currentWallpaper !== "" && !currentWallpaper.startsWith("#")) ? "file://" + currentWallpaper : ""; + return (currentWallpaper !== "" && !currentWallpaper.startsWith("#")) ? currentWallpaper : ""; } fillMode: Image.PreserveAspectCrop visible: { @@ -391,9 +391,9 @@ Item { CachingImage { anchors.fill: parent anchors.margins: 1 - source: { + imagePath: { var lightWallpaper = SessionData.wallpaperPathLight; - return (lightWallpaper !== "" && !lightWallpaper.startsWith("#")) ? "file://" + lightWallpaper : ""; + return (lightWallpaper !== "" && !lightWallpaper.startsWith("#")) ? lightWallpaper : ""; } fillMode: Image.PreserveAspectCrop visible: { @@ -575,9 +575,9 @@ Item { CachingImage { anchors.fill: parent anchors.margins: 1 - source: { + imagePath: { var darkWallpaper = SessionData.wallpaperPathDark; - return (darkWallpaper !== "" && !darkWallpaper.startsWith("#")) ? "file://" + darkWallpaper : ""; + return (darkWallpaper !== "" && !darkWallpaper.startsWith("#")) ? darkWallpaper : ""; } fillMode: Image.PreserveAspectCrop visible: { @@ -968,17 +968,9 @@ Item { SettingsDropdownRow { id: intervalDropdown - property var intervalOptions: [ - "5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", - "35 seconds", "40 seconds", "45 seconds", "50 seconds", "55 seconds", - "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 intervalOptions: ["5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", "35 seconds", "40 seconds", "45 seconds", "50 seconds", "55 seconds", "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: [ - 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, - 300, 900, 1800, 3600, 5400, 7200, 10800, 14400, 21600, 28800, 43200 - ] + property var intervalValues: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 300, 900, 1800, 3600, 5400, 7200, 10800, 14400, 21600, 28800, 43200] tab: "wallpaper" tags: ["interval", "cycling", "time", "frequency"] settingKey: "wallpaperCyclingInterval" diff --git a/quickshell/Modules/WallpaperBackground.qml b/quickshell/Modules/WallpaperBackground.qml index 759b3185..4844669e 100644 --- a/quickshell/Modules/WallpaperBackground.qml +++ b/quickshell/Modules/WallpaperBackground.qml @@ -39,6 +39,12 @@ Variants { id: root anchors.fill: parent + function encodeFileUrl(path) { + if (!path) + return ""; + return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/'); + } + property string source: SessionData.getMonitorWallpaper(modelData.name) || "" property bool isColorSource: source.startsWith("#") property string transitionType: SessionData.wallpaperTransition @@ -108,7 +114,7 @@ Variants { isInitialized = true; return; } - const formattedSource = source.startsWith("file://") ? source : "file://" + source; + const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source); setWallpaperImmediate(formattedSource); isInitialized = true; } @@ -119,7 +125,7 @@ Variants { return; } - const formattedSource = source.startsWith("file://") ? source : "file://" + source; + const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source); if (!isInitialized || !currentWallpaper.source) { setWallpaperImmediate(formattedSource); diff --git a/quickshell/Widgets/CachingImage.qml b/quickshell/Widgets/CachingImage.qml index bc73e4e3..95f82191 100644 --- a/quickshell/Widgets/CachingImage.qml +++ b/quickshell/Widgets/CachingImage.qml @@ -20,6 +20,7 @@ Image { readonly property string imageHash: imagePath ? djb2Hash(imagePath) : "" readonly property string cachePath: imageHash ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : "" + readonly property string encodedImagePath: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : "" asynchronous: true fillMode: Image.PreserveAspectCrop @@ -33,15 +34,15 @@ Image { return; } Paths.mkdir(Paths.imagecache); - source = cachePath || imagePath; + source = cachePath || encodedImagePath; } onStatusChanged: { if (source == cachePath && status === Image.Error) { - source = imagePath; + source = encodedImagePath; return; } - if (source != imagePath || status !== Image.Ready || !cachePath) + if (source != encodedImagePath || status !== Image.Ready || !cachePath) return; Paths.mkdir(Paths.imagecache); const grabPath = cachePath;