Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .config/hypr/default_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"openGuideAtStartup": true,
"topbarHelpIcon": true,
"workspaceCount": 8,
"lockTimeout": 10,
"sleepTimeout": 20,
"wallpaperDir": "",
"language": "us",
"kbOptions": "grp:alt_shift_toggle",
Expand Down
10 changes: 8 additions & 2 deletions .config/hypr/scripts/quickshell/Config.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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: ""
Expand All @@ -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);
Expand Down Expand Up @@ -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)) {
Expand Down
162 changes: 160 additions & 2 deletions .config/hypr/scripts/quickshell/settings/SettingsPopup.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down Expand Up @@ -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
Expand All @@ -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();
}
}
}
}
}
}
}
}
}
}
}
}
}

Expand Down
18 changes: 18 additions & 0 deletions .config/hypr/scripts/settings_watcher.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions .config/hypr/templates/hypridle.conf.template
Original file line number Diff line number Diff line change
@@ -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
}