diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index 28ccdc5b2..4ed1055d7 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -42,7 +42,12 @@ Variants { // Wallpaper property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || 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 bool wallpaperSafetyTriggered: { + const enabled = Config.options.workSafety.enable.wallpaper + const sensitiveWallpaper = (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.workSafety.triggerCondition.fileKeywords)) + const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)) + return enabled && sensitiveWallpaper && sensitiveNetwork; + } 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 diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 559148909..e7b595d44 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -141,13 +141,6 @@ Singleton { property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size property bool enableSidebar: 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: ["airport", "cafe", "college", "company", "eduroam", "free", "guest", "public", "school", "university"] - } - } } property JsonObject bar: JsonObject { @@ -379,6 +372,18 @@ Singleton { property JsonObject screenshotTool: JsonObject { property bool showContentRegions: true } + + property JsonObject workSafety: JsonObject { + property JsonObject enable: JsonObject { + property bool wallpaper: true + property bool clipboard: true + } + property JsonObject triggerCondition: JsonObject { + property list networkNameKeywords: ["airport", "cafe", "college", "company", "eduroam", "free", "guest", "public", "school", "university"] + property list fileKeywords: ["anime", "ecchi", "hentai", "yande.re", "konachan", "breast", "nipples", "pussy", "nsfw", "spoiler", "girl"] + property list linkKeywords: ["hentai", "porn", "sukebei", "hitomi.la", "rule34", "gelbooru", "fanbox", "dlsite"] + } + } } } } diff --git a/.config/quickshell/ii/modules/common/widgets/CliphistImage.qml b/.config/quickshell/ii/modules/common/widgets/CliphistImage.qml index e72954622..339cd4e6c 100644 --- a/.config/quickshell/ii/modules/common/widgets/CliphistImage.qml +++ b/.config/quickshell/ii/modules/common/widgets/CliphistImage.qml @@ -12,6 +12,8 @@ Rectangle { property string entry property real maxWidth property real maxHeight + property bool blur: false + property string blurText: "Image hidden" property string imageDecodePath: Directories.cliphistDecode property string imageDecodeFileName: `${entryNumber}` @@ -19,26 +21,25 @@ Rectangle { property string source property int entryNumber: { - if (!root.entry) return 0 - const match = root.entry.match(/^(\d+)\t/) - return match ? parseInt(match[1]) : 0 + if (!root.entry) + return 0; + const match = root.entry.match(/^(\d+)\t/); + return match ? parseInt(match[1]) : 0; } property int imageWidth: { - if (!root.entry) return 0 - const match = root.entry.match(/(\d+)x(\d+)/) - return match ? parseInt(match[1]) : 0 + if (!root.entry) + return 0; + const match = root.entry.match(/(\d+)x(\d+)/); + return match ? parseInt(match[1]) : 0; } property int imageHeight: { - if (!root.entry) return 0 - const match = root.entry.match(/(\d+)x(\d+)/) - return match ? parseInt(match[2]) : 0 + if (!root.entry) + return 0; + const match = root.entry.match(/(\d+)x(\d+)/); + return match ? parseInt(match[2]) : 0; } property real scale: { - return Math.min( - root.maxWidth / imageWidth, - root.maxHeight / imageHeight, - 1 - ) + return Math.min(root.maxWidth / imageWidth, root.maxHeight / imageHeight, 1); } color: Appearance.colors.colLayer1 @@ -47,26 +48,33 @@ Rectangle { implicitWidth: imageWidth * scale Component.onCompleted: { - decodeImageProcess.running = true + decodeImageProcess.running = true; } Process { id: decodeImageProcess - command: ["bash", "-c", - `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'` - ] + command: ["bash", "-c", `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`] onExited: (exitCode, exitStatus) => { if (exitCode === 0) { - root.source = imageDecodeFilePath + root.source = imageDecodeFilePath; } else { - console.error("[CliphistImage] Failed to decode image for entry:", root.entry) - root.source = "" + console.error("[CliphistImage] Failed to decode image for entry:", root.entry); + root.source = ""; } } } Component.onDestruction: { - Quickshell.execDetached(["bash", "-c", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`]) + Quickshell.execDetached(["bash", "-c", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`]); + } + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: image.width + height: image.height + radius: root.radius + } } Image { @@ -82,15 +90,42 @@ Rectangle { height: root.imageHeight * root.scale sourceSize.width: width sourceSize.height: height + } - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: image.width - height: image.height - radius: root.radius + Loader { + id: blurLoader + active: root.blur + anchors.fill: image + sourceComponent: GaussianBlur { + source: image + radius: 35 + samples: radius * 2 + 1 + + Rectangle { + anchors.fill: parent + color: ColorUtils.transparentize(Appearance.colors.colLayer0, 0.5) + + Column { + anchors { + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + } + MaterialSymbol { + visible: width <= image.width + anchors.horizontalCenter: parent.horizontalCenter + text: "visibility_off" + font.pixelSize: 28 + } + StyledText { + visible: width <= image.width + anchors.horizontalCenter: parent.horizontalCenter + text: root.blurText + color: Appearance.colors.colOnSurface + font.pixelSize: Appearance.font.pixelSize.smallie + } + } } } } } - diff --git a/.config/quickshell/ii/modules/overview/SearchItem.qml b/.config/quickshell/ii/modules/overview/SearchItem.qml index 1abdb598d..46b470de6 100644 --- a/.config/quickshell/ii/modules/overview/SearchItem.qml +++ b/.config/quickshell/ii/modules/overview/SearchItem.qml @@ -24,6 +24,8 @@ RippleButton { property string bigText: entry?.bigText ?? "" property string materialSymbol: entry?.materialSymbol ?? "" property string cliphistRawString: entry?.cliphistRawString ?? "" + property bool blurImage: entry?.blurImage ?? false + property string blurImageText: entry?.blurImageText ?? "Image hidden" visible: root.entryShown property int horizontalMargin: 10 @@ -208,6 +210,8 @@ RippleButton { entry: root.cliphistRawString maxWidth: contentColumn.width maxHeight: 140 + blur: root.blurImage + blurText: root.blurImageText } } } diff --git a/.config/quickshell/ii/modules/overview/SearchWidget.qml b/.config/quickshell/ii/modules/overview/SearchWidget.qml index 85b7d58cb..c857c0676 100644 --- a/.config/quickshell/ii/modules/overview/SearchWidget.qml +++ b/.config/quickshell/ii/modules/overview/SearchWidget.qml @@ -20,20 +20,10 @@ Item { // Wrapper implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 property string mathResult: "" - - function disableExpandAnimation() { - searchWidthBehavior.enabled = false; - } - - function cancelSearch() { - searchInput.selectAll(); - root.searchingText = ""; - searchWidthBehavior.enabled = true; - } - - function setSearchingText(text) { - searchInput.text = text; - root.searchingText = text; + property bool clipboardWorkSafetyActive: { + const enabled = Config.options.workSafety.enable.clipboard; + const sensitiveNetwork = (StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)) + return enabled && sensitiveNetwork; } property var searchActions: [ @@ -97,6 +87,27 @@ Item { // Wrapper appResults.currentIndex = 0; } + function disableExpandAnimation() { + searchWidthBehavior.enabled = false; + } + + function cancelSearch() { + searchInput.selectAll(); + root.searchingText = ""; + searchWidthBehavior.enabled = true; + } + + function setSearchingText(text) { + searchInput.text = text; + root.searchingText = text; + } + + function containsUnsafeLink(entry) { + if (entry == undefined) return false; + const unsafeKeywords = Config.options.workSafety.triggerCondition.linkKeywords; + return StringUtils.stringListContainsSubstring(entry.toLowerCase(), unsafeKeywords); + } + Timer { id: nonAppResultsTimer interval: Config.options.search.nonAppResultDelay @@ -311,7 +322,12 @@ Item { // Wrapper if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) { // Clipboard const searchString = root.searchingText.slice(Config.options.search.prefix.clipboard.length); - return Cliphist.fuzzyQuery(searchString).map(entry => { + return Cliphist.fuzzyQuery(searchString).map((entry, index, array) => { + const mightBlurImage = Cliphist.entryIsImage(entry) && root.clipboardWorkSafetyActive; + let shouldBlurImage = mightBlurImage; + if (mightBlurImage) { + shouldBlurImage = shouldBlurImage && (containsUnsafeLink(array[index - 1]) || containsUnsafeLink(array[index + 1])); + } return { cliphistRawString: entry, name: StringUtils.cleanCliphistEntry(entry), @@ -335,7 +351,9 @@ Item { // Wrapper Cliphist.deleteEntry(entry); } } - ] + ], + blurImage: shouldBlurImage, + blurImageText: Translation.tr("Work safety") }; }).filter(Boolean); }