From 78339e6e83b28d8705dfe5e42033e7c4100dc1fc Mon Sep 17 00:00:00 2001 From: fezzik-the-giant Date: Thu, 21 May 2026 17:08:58 -0400 Subject: [PATCH] Feature: Added inputs in the Settings module to allow users to set custom hypridle lock and sleep times. --- .config/hypr/default_settings.json | 2 + .config/hypr/scripts/quickshell/Config.qml | 10 +- .../quickshell/settings/SettingsPopup.qml | 162 +++++++++++++++++- .config/hypr/scripts/settings_watcher.sh | 18 ++ .config/hypr/templates/hypridle.conf.template | 19 ++ 5 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 .config/hypr/templates/hypridle.conf.template diff --git a/.config/hypr/default_settings.json b/.config/hypr/default_settings.json index 4b6d27f..1fcead2 100644 --- a/.config/hypr/default_settings.json +++ b/.config/hypr/default_settings.json @@ -3,6 +3,8 @@ "openGuideAtStartup": true, "topbarHelpIcon": true, "workspaceCount": 8, + "lockTimeout": 10, + "sleepTimeout": 20, "wallpaperDir": "", "language": "us", "kbOptions": "grp:alt_shift_toggle", diff --git a/.config/hypr/scripts/quickshell/Config.qml b/.config/hypr/scripts/quickshell/Config.qml index f3a720a..50a54cc 100644 --- a/.config/hypr/scripts/quickshell/Config.qml +++ b/.config/hypr/scripts/quickshell/Config.qml @@ -89,6 +89,8 @@ Item { property string wallpaperDir: Quickshell.env("WALLPAPER_DIR") || (homeDir + "/Pictures/Wallpapers") property string language: "" property string kbOptions: "grp:alt_shift_toggle" + property int lockTimeout: 10 + property int sleepTimeout: 20 property string weatherUnit: "metric" property string weatherApiKey: "" @@ -111,7 +113,9 @@ Item { "wallpaperDir": config.wallpaperDir, "language": config.language, "kbOptions": config.kbOptions, - "workspaceCount": config.workspaceCount + "workspaceCount": config.workspaceCount, + "lockTimeout": config.lockTimeout, + "sleepTimeout": config.sleepTimeout }; config.updateJsonBulk(configObj); @@ -376,8 +380,10 @@ Item { if (config.rawSettings.kbOptions !== undefined) config.kbOptions = config.rawSettings.kbOptions; if (config.rawSettings.workspaceCount !== undefined) { config.workspaceCount = config.rawSettings.workspaceCount; - config.initialWorkspaceCount = config.rawSettings.workspaceCount; + config.initialWorkspaceCount = config.rawSettings.workspaceCount; } + if (config.rawSettings.lockTimeout !== undefined) config.lockTimeout = config.rawSettings.lockTimeout; + if (config.rawSettings.sleepTimeout !== undefined) config.sleepTimeout = config.rawSettings.sleepTimeout; // Map Keybinds if (config.rawSettings.keybinds !== undefined && Array.isArray(config.rawSettings.keybinds)) { diff --git a/.config/hypr/scripts/quickshell/settings/SettingsPopup.qml b/.config/hypr/scripts/quickshell/settings/SettingsPopup.qml index c53a63f..7a9ba30 100644 --- a/.config/hypr/scripts/quickshell/settings/SettingsPopup.qml +++ b/.config/hypr/scripts/quickshell/settings/SettingsPopup.qml @@ -888,6 +888,8 @@ Item { { tab: 0, boxIndex: 4, label: "Layout shortcut", desc: "Toggle combination", icon: "󰯍", color: "teal" }, { tab: 0, boxIndex: 5, label: "Wallpaper directory",desc: "Absolute source path", icon: "󰋩", color: "mauve" }, { tab: 0, boxIndex: 6, label: "Workspaces", desc: "Static count in topbar", icon: "󰽿", color: "red" }, + { tab: 0, boxIndex: 7, label: "Lock timeout", desc: "Minutes until screen locks", icon: "󰌾", color: "peach" }, + { tab: 0, boxIndex: 8, label: "Sleep timeout", desc: "Minutes until system sleeps", icon: "󰒲", color: "blue" }, { tab: 1, boxIndex: 1, label: "API Key", desc: "OpenWeather API key", icon: "󰌆", color: "blue" }, { tab: 1, boxIndex: 2, label: "City ID", desc: "OpenWeather city ID", icon: "󰖐", color: "blue" }, { tab: 1, boxIndex: 3, label: "Temperature Unit", desc: "Celsius / Fahrenheit / K", icon: "󰔄", color: "blue" } @@ -1820,7 +1822,7 @@ Item { } MouseArea { id: wsMinusMa; anchors.fill: parent; hoverEnabled: true; onClicked: Config.workspaceCount = Math.max(2, Config.workspaceCount - 1) } } - Text { + Text { text: Config.workspaceCount.toString() font.family: "JetBrains Mono"; font.weight: Font.Black; font.pixelSize: root.s(14) color: box6.isActive ? root.base : root.red @@ -1845,8 +1847,164 @@ Item { } } } + + // ── Box 7: Lock timeout ────────────────────────────────── + Rectangle { + id: box7 + Layout.fillWidth: true + Layout.preferredHeight: col7lock.implicitHeight + root.s(32) + radius: root.s(12) + + property bool isActive: root.highlightedBox === 7 + color: isActive ? root.peach : root.surface0 + border.color: isActive ? root.peach : root.surface1 + border.width: 1 + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + + MouseArea { anchors.fill: parent; onClicked: root.highlightedBox = 7; z: -1 } + + ColumnLayout { + id: col7lock + anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right; anchors.margins: root.s(16) + RowLayout { + Layout.fillWidth: true; spacing: root.s(14) + Item { + Layout.preferredWidth: root.s(22); Layout.alignment: Qt.AlignTop; Layout.topMargin: root.s(2) + Text { + anchors.top: parent.top; anchors.horizontalCenter: parent.horizontalCenter + text: "󰌾"; font.family: "Iosevka Nerd Font"; font.pixelSize: root.s(18) + color: box7.isActive ? root.base : root.peach + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + } + ColumnLayout { + Layout.fillWidth: true; Layout.alignment: Qt.AlignTop; spacing: root.s(3) + Text { + text: "Lock timeout"; font.family: "Inter"; font.weight: Font.Bold; font.pixelSize: root.s(14) + color: box7.isActive ? root.base : root.text; Layout.fillWidth: true + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + Text { + text: "Minutes until screen locks"; font.family: "Inter"; font.pixelSize: root.s(11) + color: box7.isActive ? Qt.alpha(root.base, 0.75) : Qt.alpha(root.subtext0, 0.7); Layout.fillWidth: true + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + Rectangle { + Layout.fillWidth: true; Layout.preferredHeight: root.s(34); Layout.topMargin: root.s(8) + radius: root.s(7) + color: box7.isActive ? Qt.alpha(root.base, 0.15) : root.surface0 + border.color: lockTimeoutInput.activeFocus + ? (box7.isActive ? root.base : root.peach) + : (box7.isActive ? Qt.alpha(root.base, 0.3) : root.surface2) + border.width: 1 + Behavior on border.color { ColorAnimation { duration: 200 } } + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + TextInput { + id: lockTimeoutInput + anchors.fill: parent; anchors.margins: root.s(9) + verticalAlignment: TextInput.AlignVCenter + text: Config.lockTimeout.toString() + font.family: "JetBrains Mono"; font.pixelSize: root.s(11) + color: box7.isActive ? root.base : root.text; selectByMouse: true + validator: IntValidator { bottom: 1 } + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + onEditingFinished: { + let v = parseInt(text); + if (!isNaN(v) && v >= 1) Config.lockTimeout = v; + else text = Config.lockTimeout.toString(); + } + Connections { + target: Config + function onLockTimeoutChanged() { + if (!lockTimeoutInput.activeFocus) lockTimeoutInput.text = Config.lockTimeout.toString(); + } + } + } + } + } + } + } + } + + // ── Box 8: Sleep timeout ───────────────────────────────── + Rectangle { + id: box8 + Layout.fillWidth: true + Layout.preferredHeight: col8sleep.implicitHeight + root.s(32) + radius: root.s(12) + + property bool isActive: root.highlightedBox === 8 + color: isActive ? root.blue : root.surface0 + border.color: isActive ? root.blue : root.surface1 + border.width: 1 + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + + MouseArea { anchors.fill: parent; onClicked: root.highlightedBox = 8; z: -1 } + + ColumnLayout { + id: col8sleep + anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right; anchors.margins: root.s(16) + RowLayout { + Layout.fillWidth: true; spacing: root.s(14) + Item { + Layout.preferredWidth: root.s(22); Layout.alignment: Qt.AlignTop; Layout.topMargin: root.s(2) + Text { + anchors.top: parent.top; anchors.horizontalCenter: parent.horizontalCenter + text: "󰒲"; font.family: "Iosevka Nerd Font"; font.pixelSize: root.s(18) + color: box8.isActive ? root.base : root.blue + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + } + ColumnLayout { + Layout.fillWidth: true; Layout.alignment: Qt.AlignTop; spacing: root.s(3) + Text { + text: "Sleep timeout"; font.family: "Inter"; font.weight: Font.Bold; font.pixelSize: root.s(14) + color: box8.isActive ? root.base : root.text; Layout.fillWidth: true + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + Text { + text: "Minutes until system sleeps"; font.family: "Inter"; font.pixelSize: root.s(11) + color: box8.isActive ? Qt.alpha(root.base, 0.75) : Qt.alpha(root.subtext0, 0.7); Layout.fillWidth: true + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + Rectangle { + Layout.fillWidth: true; Layout.preferredHeight: root.s(34); Layout.topMargin: root.s(8) + radius: root.s(7) + color: box8.isActive ? Qt.alpha(root.base, 0.15) : root.surface0 + border.color: sleepTimeoutInput.activeFocus + ? (box8.isActive ? root.base : root.blue) + : (box8.isActive ? Qt.alpha(root.base, 0.3) : root.surface2) + border.width: 1 + Behavior on border.color { ColorAnimation { duration: 200 } } + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + TextInput { + id: sleepTimeoutInput + anchors.fill: parent; anchors.margins: root.s(9) + verticalAlignment: TextInput.AlignVCenter + text: Config.sleepTimeout.toString() + font.family: "JetBrains Mono"; font.pixelSize: root.s(11) + color: box8.isActive ? root.base : root.text; selectByMouse: true + validator: IntValidator { bottom: 1 } + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + onEditingFinished: { + let v = parseInt(text); + if (!isNaN(v) && v >= 1) Config.sleepTimeout = v; + else text = Config.sleepTimeout.toString(); + } + Connections { + target: Config + function onSleepTimeoutChanged() { + if (!sleepTimeoutInput.activeFocus) sleepTimeoutInput.text = Config.sleepTimeout.toString(); + } + } + } + } + } + } + } + } } - } + } } } diff --git a/.config/hypr/scripts/settings_watcher.sh b/.config/hypr/scripts/settings_watcher.sh index 4b8a649..7866ea7 100755 --- a/.config/hypr/scripts/settings_watcher.sh +++ b/.config/hypr/scripts/settings_watcher.sh @@ -13,6 +13,7 @@ AUTOSTART_CONF="$CONF_DIR/autostart.conf" ENV_CONF="$CONF_DIR/env.conf" KEYBINDS_CONF="$CONF_DIR/keybindings.conf" MONITORS_CONF="$CONF_DIR/monitors.conf" +HYPRIDLE_CONF="$HOME/.config/hypr/hypridle.conf" ZSH_RC="$HOME/.zshrc" # Ensure the required files and directories exist @@ -29,6 +30,7 @@ compile_settings() { # This means a pure uiScale/wallpaperDir/weatherApiKey write never triggers a reload. OLD_NONMON_HASH=$(md5sum "$SETTINGS_CONF" "$KEYBINDS_CONF" "$AUTOSTART_CONF" "$ENV_CONF" 2>/dev/null | md5sum) OLD_MON_HASH=$(md5sum "$MONITORS_CONF" 2>/dev/null | md5sum) + OLD_HYPRIDLE_HASH=$(md5sum "$HYPRIDLE_CONF" 2>/dev/null | md5sum) # Read state from JSON (Using 'has' to safely parse booleans) LANG=$(jq -r '.language // "us"' "$SETTINGS_FILE") @@ -100,9 +102,20 @@ compile_settings() { echo "monitor = , preferred, auto, 1" >> "$MONITORS_CONF" fi + # 6. Regenerate hypridle.conf + echo "Regenerating hypridle.conf..." + LOCK_TIMEOUT_MIN=$(jq -r '.lockTimeout // 10' "$SETTINGS_FILE") + SLEEP_TIMEOUT_MIN=$(jq -r '.sleepTimeout // 20' "$SETTINGS_FILE") + LOCK_TIMEOUT=$(awk "BEGIN {print int($LOCK_TIMEOUT_MIN * 60)}") + SLEEP_TIMEOUT=$(awk "BEGIN {print int($SLEEP_TIMEOUT_MIN * 60)}") + sed -e "s|{{LOCK_TIMEOUT}}|$LOCK_TIMEOUT|g" \ + -e "s|{{SLEEP_TIMEOUT}}|$SLEEP_TIMEOUT|g" \ + "$TMPL_DIR/hypridle.conf.template" > "$HYPRIDLE_CONF" + # Hash after changes NEW_NONMON_HASH=$(md5sum "$SETTINGS_CONF" "$KEYBINDS_CONF" "$AUTOSTART_CONF" "$ENV_CONF" 2>/dev/null | md5sum) NEW_MON_HASH=$(md5sum "$MONITORS_CONF" 2>/dev/null | md5sum) + NEW_HYPRIDLE_HASH=$(md5sum "$HYPRIDLE_CONF" 2>/dev/null | md5sum) if [ "$OLD_MON_HASH" != "$NEW_MON_HASH" ]; then # Monitor layout actually changed — full reload needed @@ -116,6 +129,11 @@ compile_settings() { # Nothing that affects Hyprland changed (e.g. uiScale, weatherApiKey) — skip reload entirely echo "No Hyprland config changes detected, skipping reload." fi + + if [ "$OLD_HYPRIDLE_HASH" != "$NEW_HYPRIDLE_HASH" ]; then + echo "Hypridle config changed, restarting hypridle..." + killall hypridle 2>/dev/null; sleep 0.2; hypridle & + fi } # If called with --compile, execute once and exit (used by install.sh) diff --git a/.config/hypr/templates/hypridle.conf.template b/.config/hypr/templates/hypridle.conf.template new file mode 100644 index 0000000..436debc --- /dev/null +++ b/.config/hypr/templates/hypridle.conf.template @@ -0,0 +1,19 @@ +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# ◈ HYPRIDLE CONFIGURATION (generated by settings_watcher.sh — do not edit directly) +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +general { + lock_cmd = quickshell -p ~/.config/hypr/scripts/quickshell/Lock.qml + before_sleep_cmd = loginctl lock-session + after_sleep_cmd = hyprctl dispatch dpms on +} + +listener { + timeout = {{LOCK_TIMEOUT}} + on-timeout = loginctl lock-session +} + +listener { + timeout = {{SLEEP_TIMEOUT}} + on-timeout = systemctl suspend +}