diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index 3df6c445c..c6cf9cd94 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -45,6 +45,10 @@ Variants { || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov") property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath + property bool wallpaperSafetyTriggered: Config.options.background.wallpaperSafety.enable && ( + CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.background.wallpaperSafety.triggerCondition.wallpaperKeywords) && + CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.background.wallpaperSafety.triggerCondition.networkNameKeywords) + ) property real wallpaperToScreenRatio: Math.min(wallpaperWidth / screen.width, wallpaperHeight / screen.height) property real preferredWallpaperScale: Config.options.background.parallax.workspaceZoom property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated @@ -57,7 +61,7 @@ Variants { property real clockX: (modelData.width / 2) property real clockY: (modelData.height / 2) property var textHorizontalAlignment: { - if (Config.options.background.lockBlur.enable && Config.options.background.lockBlur.centerClock && GlobalStates.screenLocked) + if ((Config.options.background.lockBlur.enable && Config.options.background.lockBlur.centerClock && GlobalStates.screenLocked) || wallpaperSafetyTriggered) return Text.AlignHCenter; if (clockX < screen.width / 3) return Text.AlignLeft; @@ -69,11 +73,61 @@ Variants { property bool shouldBlur: (GlobalStates.screenLocked && Config.options.background.lockBlur.enable) property color dominantColor: Appearance.colors.colPrimary property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 - property color colText: (GlobalStates.screenLocked && shouldBlur) ? Appearance.colors.colOnLayer0 : CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12)) + property color colText: { + if (wallpaperSafetyTriggered) return CF.ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colPrimary, 0.75); + return (GlobalStates.screenLocked && shouldBlur) ? Appearance.colors.colOnLayer0 : CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12)) + } Behavior on colText { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } + // Components + component ClockText: StyledText { + Layout.fillWidth: true + horizontalAlignment: bgRoot.textHorizontalAlignment + font { + family: Appearance.font.family.expressive + pixelSize: 20 + weight: Font.DemiBold + } + color: bgRoot.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + animateChange: true + } + component ClockStatusText: RowLayout { + id: statusTextRow + property alias statusIcon: statusIconWidget.text + property alias statusText: statusTextWidget.text + property bool shown: true + opacity: shown ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Layout.fillWidth: false + MaterialSymbol { + id: statusIconWidget + Layout.fillWidth: false + iconSize: Appearance.font.pixelSize.huge + color: bgRoot.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + } + ClockText { + id: statusTextWidget + Layout.fillWidth: false + color: bgRoot.colText + font { + family: Appearance.font.family.main + pixelSize: Appearance.font.pixelSize.large + weight: Font.Normal + } + style: Text.Raised + styleColor: Appearance.colors.colShadow + } + } + // Layer props screen: modelData exclusionMode: ExclusionMode.Ignore @@ -86,7 +140,10 @@ Variants { left: true right: true } - color: "transparent" + color: CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75); + Behavior on color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } onWallpaperPathChanged: { bgRoot.updateZoomScale() @@ -206,7 +263,12 @@ Variants { property real effectiveValueY: Math.max(0, Math.min(1, valueY)) x: -(bgRoot.movableXSpace) - (effectiveValueX - 0.5) * 2 * bgRoot.movableXSpace y: -(bgRoot.movableYSpace) - (effectiveValueY - 0.5) * 2 * bgRoot.movableYSpace - source: bgRoot.wallpaperPath + source: { + print("-----------------") + print("Safety triggered:", bgRoot.wallpaperSafetyTriggered); + print("Wallpaper path:", bgRoot.wallpaperPath); + return bgRoot.wallpaperSafetyTriggered ? "" : bgRoot.wallpaperPath + } fillMode: Image.PreserveAspectCrop Behavior on x { NumberAnimation { @@ -278,13 +340,14 @@ Variants { } states: State { name: "centered" - when: bgRoot.shouldBlur && Config.options.background.lockBlur.centerClock + when: (bgRoot.shouldBlur && Config.options.background.lockBlur.centerClock) || bgRoot.wallpaperSafetyTriggered AnchorChanges { target: clockLoader anchors { left: undefined right: undefined - top: parent.top + top: undefined + verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter } } @@ -306,46 +369,27 @@ Variants { anchors.centerIn: parent spacing: 6 - StyledText { - Layout.fillWidth: true - horizontalAlignment: bgRoot.textHorizontalAlignment - font { - family: Appearance.font.family.expressive - pixelSize: 90 - weight: Font.Bold - } - color: bgRoot.colText - style: Text.Raised - styleColor: Appearance.colors.colShadow + ClockText { + font.pixelSize: 90 text: DateTime.time } - StyledText { - Layout.fillWidth: true + ClockText { Layout.topMargin: -5 - horizontalAlignment: bgRoot.textHorizontalAlignment - font { - family: Appearance.font.family.expressive - pixelSize: 20 - weight: Font.DemiBold - } - color: bgRoot.colText - style: Text.Raised - styleColor: Appearance.colors.colShadow text: DateTime.date - animateChange: true } - StyledText { + StyledText { // Somehow gets fucked up if made a ClockText??? Layout.fillWidth: true horizontalAlignment: bgRoot.textHorizontalAlignment font { - family: Appearance.font.family.expressive - pixelSize: 20 - weight: Font.DemiBold + family: Appearance.font.family.main + pixelSize: Appearance.font.pixelSize.normal + weight: 350 + italic: true } color: bgRoot.colText style: Text.Raised - visible: Config.options.background.quote !== "" styleColor: Appearance.colors.colShadow + // visible: Config.options.background.quote.length > 0 text: Config.options.background.quote } } @@ -356,31 +400,21 @@ Variants { left: bgRoot.textHorizontalAlignment === Text.AlignLeft ? clockColumn.left : undefined right: bgRoot.textHorizontalAlignment === Text.AlignRight ? clockColumn.right : undefined horizontalCenter: bgRoot.textHorizontalAlignment === Text.AlignHCenter ? clockColumn.horizontalCenter : undefined - topMargin: 5 - leftMargin: -5 - rightMargin: -5 - } - opacity: GlobalStates.screenLocked && (!Config.options.background.lockBlur.enable || Config.options.background.lockBlur.showLockedText) ? 1 : 0 - visible: opacity > 0 - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + topMargin: 14 + leftMargin: -6 + rightMargin: -6 } + spacing: 16 Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft; implicitWidth: 1 } - MaterialSymbol { - text: "lock" - Layout.fillWidth: false - iconSize: Appearance.font.pixelSize.huge - color: bgRoot.colText - style: Text.Raised - styleColor: Appearance.colors.colShadow + ClockStatusText { + shown: bgRoot.wallpaperSafetyTriggered + statusIcon: "hide_image" + statusText: qsTr("Wallpaper safety enforced") } - StyledText { - Layout.fillWidth: false - text: "Locked" - color: bgRoot.colText - font.pixelSize: Appearance.font.pixelSize.larger - style: Text.Raised - styleColor: Appearance.colors.colShadow + ClockStatusText { + shown: GlobalStates.screenLocked && (!Config.options.background.lockBlur.enable || Config.options.background.lockBlur.showLockedText) + statusIcon: "lock" + statusText: qsTr("Locked") } Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight; implicitWidth: 1 } diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 878fdcd02..c3233a722 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -128,6 +128,8 @@ Singleton { property bool showClock: true property string wallpaperPath: "" property string thumbnailPath: "" + property string quote: "" + property bool hideWhenFullscreen: true property JsonObject parallax: JsonObject { property bool vertical: false property bool autoVertical: false @@ -142,8 +144,13 @@ Singleton { property bool showLockedText: true property real extraZoom: 1.1 } - property string quote: "" - property bool hideWhenFullscreen: true + property JsonObject wallpaperSafety: JsonObject { + property bool enable: true + property JsonObject triggerCondition: JsonObject { + property list wallpaperKeywords: ["anime", "ecchi", "hentai", "yande.re", "konachan", "breast", "nipples", "pussy", "nsfw", "spoiler", "girl"] + property list networkNameKeywords: ["guest", "public", "free", "airport"] + } + } } property JsonObject bar: JsonObject { diff --git a/.config/quickshell/ii/modules/common/functions/StringUtils.qml b/.config/quickshell/ii/modules/common/functions/StringUtils.qml index ea5e42df9..d3158a46c 100644 --- a/.config/quickshell/ii/modules/common/functions/StringUtils.qml +++ b/.config/quickshell/ii/modules/common/functions/StringUtils.qml @@ -8,7 +8,7 @@ Singleton { * Formats a string according to the args that are passed inc * @param { string } str * @param {...any} args - * @returns + * @returns { string } */ function format(str, ...args) { return str.replace(/{(\d+)}/g, (match, index) => typeof args[index] !== 'undefined' ? args[index] : match); @@ -35,10 +35,10 @@ Singleton { } /** - * Escapes single quotes in shell commands - * @param { string } str - * @returns { string } - */ + * Escapes single quotes in shell commands + * @param { string } str + * @returns { string } + */ function shellSingleQuoteEscape(str) { return String(str) // .replace(/\\/g, '\\\\') @@ -48,6 +48,7 @@ Singleton { /** * Splits markdown blocks into three different types: text, think, and code. * @param { string } markdown + * @returns {Array<{type: "text" | "think" | "code", content: string, lang?: string, completed?: boolean}>} */ function splitMarkdownBlocks(markdown) { const regex = /```(\w+)?\n([\s\S]*?)```|([\s\S]*?)<\/think>/g; @@ -182,6 +183,11 @@ Singleton { return lines.join("\n"); } + /** + * Cleans up a music title by removing bracketed and special characters. + * @param { string } title + * @returns { string } + */ function cleanMusicTitle(title) { if (!title) return ""; @@ -198,6 +204,11 @@ Singleton { return title.trim(); } + /** + * Converts seconds to a friendly time string (e.g. 1:23 or 1:02:03). + * @param { number } seconds + * @returns { string } + */ function friendlyTimeForSeconds(seconds) { if (isNaN(seconds) || seconds < 0) return "0:00"; @@ -212,13 +223,38 @@ Singleton { } } + /** + * Escapes HTML special characters in a string. + * @param { string } str + * @returns { string } + */ function escapeHtml(str) { if (typeof str !== 'string') return str; return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); } + /** + * Cleans a cliphist entry by removing leading digits and tab. + * @param { string } str + * @returns { string } + */ function cleanCliphistEntry(str: string): string { return str.replace(/^\d+\t/, ""); } + + /** + * Checks if any substring in the list is contained in the string. + * @param { string } str + * @param { string[] } substrings + * @returns { boolean } + */ + function stringListContainsSubstring(str, substrings) { + for (let i = 0; i < substrings.length; ++i) { + if (str.includes(substrings[i])) { + return true; + } + } + return false; + } }