diff --git a/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml b/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml index 97a143e58..317cfac11 100644 --- a/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml +++ b/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml @@ -8,6 +8,7 @@ import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects +import Quickshell.Io import "./dateIndicator" import "./minuteMarks" @@ -35,6 +36,54 @@ Item { implicitWidth: implicitSize implicitHeight: implicitSize + function applyStyle(sides, dialStyle, hourHandStyle, minuteHandStyle, secondHandStyle, dateStyle) { + Config.options.background.clock.cookie.sides = sides + Config.options.background.clock.cookie.dialNumberStyle = dialStyle + Config.options.background.clock.cookie.hourHandStyle = hourHandStyle + Config.options.background.clock.cookie.minuteHandStyle = minuteHandStyle + Config.options.background.clock.cookie.secondHandStyle = secondHandStyle + Config.options.background.clock.cookie.dateStyle = dateStyle + } + + function setClockPreset(category) { + if (!Config.options.background.clock.cookie.aiStyling) return; + if (category === "") return; + print("[Cookie clock] Setting clock preset for category: " + category) + // "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space" + if (category == "abstract") { + applyStyle(7, "dots", "fill", "medium", "dot", "bubble") + } else if (category == "anime") { + applyStyle(12, "dots", "fill", "bold", "dot", "bubble") + } else if (category == "city" || category == "space") { + applyStyle(23, "full", "hollow", "medium", "classic", "bubble") + } else if (category == "minimalist") { + applyStyle(6, "none", "fill", "bold", "dot", "hide") + } else if (category == "landscape") { + applyStyle(14, "full", "hollow", "medium", "classic", "bubble") + } else if (category == "plants") { + applyStyle(9, "dots", "fill", "bold", "dot", "border") + } else if (category == "person") { + applyStyle(14, "full", "classic", "classic", "classic", "rect") + } + } + + Connections { + target: Config + function onReadyChanged() { + categoryFileView.path = Directories.generatedWallpaperCategoryPath + } + } + + FileView { + id: categoryFileView + path: "" + watchChanges: true + onFileChanged: reload() + onLoaded: { + root.setClockPreset(categoryFileView.text().trim()) + } + } + DropShadow { source: cookie anchors.fill: source diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index c1aea922e..9bbe53079 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -40,11 +40,30 @@ Singleton { obj[keys[keys.length - 1]] = convertedValue; } + Timer { + id: fileReloadTimer + interval: 100 + repeat: false + onTriggered: { + configFileView.reload() + } + } + + Timer { + id: fileWriteTimer + interval: 100 + repeat: false + onTriggered: { + configFileView.writeAdapter() + } + } + FileView { + id: configFileView path: root.filePath watchChanges: true - onFileChanged: reload() - onAdapterUpdated: writeAdapter() + onFileChanged: fileReloadTimer.restart() + onAdapterUpdated: fileWriteTimer.restart() onLoaded: root.ready = true onLoadFailed: error => { if (error == FileViewError.FileNotFound) { @@ -130,11 +149,12 @@ Singleton { property string style: "cookie" // Options: "cookie", "digital" property real scale: 1 property JsonObject cookie: JsonObject { + property bool aiStyling: false property int sides: 14 property string dialNumberStyle: "full" // Options: "dots" , "numbers", "full" , "none" property string hourHandStyle: "fill" // Options: "classic", "fill", "hollow", "hide" property string minuteHandStyle: "medium" // Options "classic", "thin", "medium", "bold", "hide" - property string secondHandStyle: "dot" // Options: "dot", "line" , "hide" + property string secondHandStyle: "dot" // Options: "dot", "line", "classic", "hide" property string dateStyle: "bubble" // Options: "border", "rect", "bubble" , "hide" property bool timeIndicators: true property bool hourMarks: false diff --git a/.config/quickshell/ii/modules/common/Directories.qml b/.config/quickshell/ii/modules/common/Directories.qml index 20d95a33f..9b113de8d 100644 --- a/.config/quickshell/ii/modules/common/Directories.qml +++ b/.config/quickshell/ii/modules/common/Directories.qml @@ -34,6 +34,7 @@ Singleton { property string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`) property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`) property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`) + property string generatedWallpaperCategoryPath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/wallpaper/category.txt`) property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`) property string screenshotTemp: "/tmp/quickshell/media/screenshot" property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`) diff --git a/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index a4e51add5..6828625f5 100644 --- a/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -59,8 +59,20 @@ ContentPage { ContentSubsection { visible: Config.options.background.clock.style === "cookie" title: Translation.tr("Cookie clock settings") + + ConfigSwitch { + buttonIcon: "wand_stars" + text: Translation.tr("Auto styling with Gemini") + checked: Config.options.background.clock.cookie.aiStyling + onCheckedChanged: { + Config.options.background.clock.cookie.aiStyling = checked; + } + StyledToolTip { + text: "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information." + } + } + ConfigSpinBox { - visible: Config.options.background.clock.style === "cookie" icon: "add_triangle" text: Translation.tr("Sides") value: Config.options.background.clock.cookie.sides @@ -73,7 +85,6 @@ ContentPage { } ConfigSwitch { - visible: Config.options.background.clock.style === "cookie" buttonIcon: "autoplay" text: Translation.tr("Constantly rotate") checked: Config.options.background.clock.cookie.constantlyRotate @@ -86,7 +97,6 @@ ContentPage { } ConfigRow { - visible: Config.options.background.clock.style === "cookie" ConfigSwitch { enabled: Config.options.background.clock.style === "cookie" && Config.options.background.clock.cookie.dialNumberStyle === "dots" || Config.options.background.clock.cookie.dialNumberStyle === "full" diff --git a/.config/quickshell/ii/scripts/ai/gemini-categorize-wallpaper.sh b/.config/quickshell/ii/scripts/ai/gemini-categorize-wallpaper.sh new file mode 100755 index 000000000..33915fb97 --- /dev/null +++ b/.config/quickshell/ii/scripts/ai/gemini-categorize-wallpaper.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +if [[ -z "$1" ]]; then + echo "Usage: $0 " + exit 1 +fi + +SOURCE_IMG_PATH="$1" +WALLPAPER_NAME="$(basename "$SOURCE_IMG_PATH")" +RESIZED_IMG_PATH="/tmp/quickshell/ai/wallpaper.jpg" +magick "$SOURCE_IMG_PATH" -resize 200x -quality 50 "$RESIZED_IMG_PATH" +API_KEY=$(secret-tool lookup 'application' 'illogical-impulse' | jq -r '.apiKeys.gemini') + +if [[ "$(base64 --version 2>&1)" = *"FreeBSD"* ]]; then +B64FLAGS="--input" +else +B64FLAGS="-w0" +fi + +payload='{ + "contents": [{ + "parts":[ + { + "inline_data": { + "mime_type":"image/jpeg", + "data": "'"$(base64 $B64FLAGS $RESIZED_IMG_PATH)"'" + } + }, + {"text": "Categorize the wallpaper. Its file name is '"$WALLPAPER_NAME"'"} + ] + }], + "generationConfig": { + "responseMimeType": "text/x.enum", + "responseSchema": { + "type": "string", + "enum": [ "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space" ] + }, + "temperature": 0, + } +}' +# echo "$payload" | jq +response=$(curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent" \ +-H "x-goog-api-key: $API_KEY" \ +-H 'Content-Type: application/json' \ +-X POST \ +-d "$payload" 2> /dev/null) + +echo "$response" | jq -r '.candidates[0].content.parts[0].text' diff --git a/.config/quickshell/ii/scripts/colors/switchwall.sh b/.config/quickshell/ii/scripts/colors/switchwall.sh index 794b2d0e7..5d1a57c7a 100755 --- a/.config/quickshell/ii/scripts/colors/switchwall.sh +++ b/.config/quickshell/ii/scripts/colors/switchwall.sh @@ -171,6 +171,13 @@ switch() { type_flag="$3" color_flag="$4" color="$5" + + # Start Gemini auto-categorization if enabled + aiStylingEnabled=$(jq -r '.background.clock.cookie.aiStyling' "$SHELL_CONFIG_FILE") + if [[ "$aiStylingEnabled" == "true" ]]; then + "$SCRIPT_DIR/../ai/gemini-categorize-wallpaper.sh" "$imgpath" > "$STATE_DIR/user/generated/wallpaper/category.txt" & + fi + read scale screenx screeny screensizey < <(hyprctl monitors -j | jq '.[] | select(.focused) | .scale, .x, .y, .height' | xargs) cursorposx=$(hyprctl cursorpos -j | jq '.x' 2>/dev/null) || cursorposx=960 cursorposx=$(bc <<< "scale=0; ($cursorposx - $screenx) * $scale / 1")